Passer un bloc de code comme un anon. une fonction

9

Est-il possible de traiter un bloc de commandes comme une fonction anonyme?

function wrap_this {
   run_something
   # Decide to run block or maybe not.
   run_something else
}

wrap_this {
   do_something
   do_somthing else
}

# Do something else

wrap_this {
   do_something_else_else
   do_something_else_else_else
}

(Je me rends compte que vous créez une fonction ou un fichier pour chaque bloc, mais je trouve cette option plus claire et plus facile à lire dans certaines situations.)

whilele fait avec do/doneet le functionfait avec { multiple lines }. Je me rends compte que BASH n'a pas de fonctions anonymes, mais est-il possible de passer plusieurs commandes à une autre fonction, comme vous pouvez le faire lors de la définition d'une fonction ou while?

dgo.a
la source
Voulez-vous dire que vous voulez décorer (en langage Python) - c'est-à-dire renvoyer une fonction à partir d'une fonction? Votre exemple, syntaxiquement, n'est même pas BASH: wrap_this est-il censé être une fonction ou un appel de fonction?
Mel Boyce
Je ne sais pas trop ce que vous voulez faire. Comme Mel l'a souligné, ce que vous avez écrit est même valable du point de vue syntaxique, mais je ne sais pas exactement comment ce que vous avez écrit se rapporte aux fonctions anonymes.
Chris Down

Réponses:

2

C'est la solution la plus courte à laquelle je pouvais penser:

Compte tenu de ces fonctions:

# List processing
map() { while IFS='' read -r x; do "$@" "$x"; done; }
filter() { while IFS='' read -r x; do "$@" "$x" >&2 && echo "$x"; done; }
foldr() { local f="$1"; local result="$2"; shift 2;  while IFS='' read -r x; do result="$( "$f" "$@" "$x" "$result" )"; done; echo "$result"; }
foldl() { local f="$1"; local result="$2"; shift 2;  while IFS='' read -r x; do result="$( "$f" "$@" "$result" "$x" )"; done; echo "$result"; }

# Helpers
re() { [[ "$2" =~ $1 ]]; }

Exemples:

# Example helpers
toLower() { tr '[:upper:]' '[:lower:]'; }
showStructure() { [[ "$1" == "--curly" ]] && echo "{$2; $3}" || echo "($1, $2)"; }

# All lib* directories, ignoring case, using regex
ls /usr | map toLower | filter re 'lib.*'

# All block devices. (Using test, for lack of a full bash [[ … ]].)
cd /dev; ls | filter test -b

# Show difference between foldr and foldl
$ ls / | foldr showStructure '()'
(var/, (usr/, (tmp/, (sys/, (sbin/, (run/, (root/, (proc/, (opt/, (mnt/, (media/, (lost+found/, (lib64/, (lib32/, (lib@, (home/, (etc/, (dev/, (daten/, (boot/, (bin/, ())))))))))))))))))))))
$ ls / | foldr showStructure '{}' --curly
{var/; {usr/; {tmp/; {sys/; {sbin/; {run/; {root/; {proc/; {opt/; {mnt/; {media/; {lost+found/; {lib64/; {lib32/; {lib@; {home/; {etc/; {dev/; {daten/; {boot/; {bin/; {}}}}}}}}}}}}}}}}}}}}}}

(Ces exemples ne sont bien sûr que des exemples d'utilisation, et en réalité, ce style n'aurait de sens que pour des cas d'utilisation plus compliqués.)

Généralement, le style suivant peut toujours être utilisé:

f() { something "$@"       ; }; someList    | map    f
g() { something "$1" "$2" …; }; someCommand | filter g
                                               

Ce n'est pas tout à fait lambda, mais c'est très très proche. Seulement quelques excès de caractères.

Mais aucune des abréviations de commodité suivantes ne fonctionne pour autant que je sache:

λ() { [[ $@ ]]; } # fails on spaces
λ() { [[ "${@:-1}" ${@:1:-1} ]]; } # syntax error
alias λ=test # somehow ignored

Malheureusement, bashn'est pas très bien adapté à ce style, même si certaines de ses fonctionnalités ont un style très fonctionnel.

Evi1M4chine
la source
Les caractéristiques de bash ont un style fonctionnel? La seule chose à distance fonctionnelle est que bash (comme à peu près n'importe quel langage shell) prend en charge une forme de composition de fonctions en canalisant la sortie d'une commande vers l'entrée d'une autre. C'est plutôt une caractéristique de la conception générale d'Unix, pas bash en soi.
kyrill
4

J'ai réussi à faire ce que tu veux avec un evalhack. Sur ce, je mets en garde contre le fait que l' eval n'est pas sûr et que vous devez éviter à tout prix . Cela dit, si vous pensez que votre code ne sera pas utilisé abusivement, vous pouvez utiliser ceci:

wrap_this(){
    run_something
    eval "$(cat /dev/stdin)"
    run_something_else
}

Cela vous permet d'exécuter du code comme suit:

wrap_this << EOF
    my function
EOF

Pas exactement idéal, car le bloc interne est une chaîne, mais crée une réutilisation.

hkupty
la source
C'est génial! Je voudrais simplement ajouter des guillemets autour du premier !!afin d'empêcher la substitution dans heredoc. stackoverflow.com/questions/27920806/…
Saintali
3

Vous pouvez mettre du code dans une chaîne et le passer à evalou shou simplement l'interpoler.

perform () {
  "$@"
}

perform echo "moo"

Cependant, vous pouvez rapidement vous retrouver dans de gros problèmes de cotation.

tripleee
la source
1
En fait, je cherchais perform { echo "moo" \n echo "moo moo" \n echo "moo moo moo" }. Je savais déjà que vous pouvez passer une commande. Mais, je recherche plusieurs lignes, pas seulement une commande ou une ligne. Merci d'avoir essayé.
dgo.a
3

Non, bash n'a pas de fonctions anonymes. Il est cependant possible de passer un nom de fonction et des arguments sous forme de chaînes et que bash l'appelle.

function wrap() {
    do_before
    "$@"
    do_after
}

wrap do_something with_arguments

Ceci est cependant quelque peu limité. Le traitement des devis peut devenir un problème. Passer plusieurs commandes est également une complication.

David Baggerman
la source
1
Pas un nom de nuit, tout ce que vous avez à faire est de changer $*pour"$@"
glenn jackman
Les corps de fonctions multilignes devraient fonctionner correctement avec cette méthode.
Glenn, j'ai oublié ce formulaire, merci. Cependant, cela ne résout pas complètement le problème des citations. Evan, une seule commande enroulée sur plusieurs lignes fonctionnerait bien, je voulais dire avoir plus d'une commande dans le corps selon les exemples de la question. Je mettrai à jour ma réponse pour répondre aux deux commentaires.
David Baggerman
Ce formulaire fonctionne très bien si vous prenez soin de définir des fonctions qui acceptent des arguments simples, puis de passer des appels à ces fonctions en tant qu'arguments. C'est à dire wrap my_func "$arg1" "$arg2". Mon seul problème avec cet exemple est que la valeur de retour de "$ @" est perdue, mais cela est facilement corrigé. Il peut être souhaitable d'envelopper "$ @" avec ()pour s'assurer que les changements environnementaux ne fuient pas, à un certain coût de performance.
Michael Mol