web-dev-qa-db-fra.com

Pourquoi combiner des commandes sur une seule ligne dans un script Bash?

Je suis nouveau dans les scripts Linux et Bash. Au travail, j'ai vu des scripts Bash avec des constructions similaires à ceci:

mkdir build && cd build && touch blank.txt

Ou:

mkdir build; cd build; touch blank.txt

Ou même l'exotisme:

COMMAND="mkdir build && cd build && touch blank.txt"
eval ${COMMAND}

Le dernier exemple donne un cas d'utilisation possible où une seule ligne pourrait être utile, mais généralement ce qui suit est plus facile à lire et (au moins pour moi) vous permet de déboguer visuellement le script:

mkdir build
cd build
touch blank.txt

Y a-t-il des avantages techniques à tout entasser sur une seule ligne?

36
AlainD
mkdir build && cd build && touch blank.txt

Dans Bash (et quelques autres obus) && est une logique et , ce qui signifie - si la commande précédente renvoie vrai, la commande suivante sera exécutée. Il y a aussi logique ou ||. Par exemple, vous pouvez combiner ces deux options dans une seule instruction:

mkdir /tmp/abc/123 && echo 'the dir is created' || echo "the dir isn't created"

Remarque, la construction cmd_1 && cmd_2 || comd_3 n'est pas un substitut de if; then; else, car peu importe laquelle des commandes précédentes renvoie false, le cmd_3 sera exécuté. Vous devez donc faire attention aux circonstances dans lesquelles vous l'utilisez. Voici un exemple:

$ true && false || echo success
success
$ false && true || echo success
success
$ false && false || echo success
success

En règle générale, lorsque j'utilise la commande cd dans un script, je teste la réussite du changement de répertoire: cd somewhere/ || exit. Un test plus approprié est proposé par @dessert : if ! cd dir; then exit 1; fi. Mais dans tous les cas, comme protection contre l'échec du script, il est préférable d'utiliser le set -e comme indiqué dans la réponse de @ mrks .


mkdir build; cd build; touch blank.txt

; est un délimiteur de ligne et il est utilisé lorsque peu de commandes séparées sont écrites sur une seule ligne.


Notez quand ; ou && et || sont en cours d'utilisation, il n'est pas obligatoire d'écrire les commandes en ligne - cela est illustré dans le @ allo's answer .

Du tout, OMI, il n'y a aucun avantage/différence technique particulier entre l'écriture des commandes sur une ou sur des lignes distinctes.


COMMAND="mkdir build && cd build && touch blank.txt"
eval ${COMMAND}

Ici, une ou plusieurs commandes (et leurs arguments) sont regroupées en tant que valeur d'une variable, puis cette variable (argument) est convertie en commande à l'aide de eval. Ainsi, vous pourrez modifier la commande qui sera exécutée plusieurs fois dans un script en la modifiant à un seul endroit.

Disons que vous devez changer la façon dont le fichier blank.txt est créé, par exemple, vous pouvez modifier la ligne appropriée de la manière suivante:

COMMAND="mkdir build && cd build && echo 'test' > blank.txt"

Le réel evalavantage par rapport à l'utilisation de alias est quand il y a des réorientations, des tuyaux, des opérateurs logiques, etc. sont utilisés. Dans la plupart des cas, vous pouvez utiliser functions au lieu de eval lorsque alias n'est pas applicable. Même si la variable ne contient qu'une seule commande, c'est-à-dire CMD="cat", nous n'avons pas besoin de eval, car Bash world-split va se développer "$CMD" "$file1" "$file2" correctement.

La version précédente de cette section, discutée dans les commentaires est disponible ici .

53
pa4080

Les réponses jusqu'à présent portent sur ce qui se passe/comment cela fonctionne, mais je pense que vous demandiez "pourquoi"

La principale "raison" de le faire (pour moi) plutôt que de les taper sur trois lignes est que parfois les commandes prennent du temps (parfois des minutes) et que vous ne voulez pas traîner pendant tout le temps.

Par exemple, je pourrais construire && déployer && lancez mon application puis sortez pour le déjeuner. Le processus peut prendre 15 minutes. Cependant, depuis que j'ai utilisé &&, il ne se déploiera pas si la construction échoue - donc ça marche.

La saisie anticipée est une alternative mais elle est incertaine, vous pourriez ne pas être en mesure de voir ce que vous tapez (et donc faire des erreurs) ou un programme pourrait manger la saisie anticipée (Merci, Grails!). Ensuite, vous revenez du déjeuner et découvrez que vous avez encore deux tâches assez longues à démarrer à cause d'une faute de frappe.

L'autre alternative consiste à écrire un petit script ou alias. Pas une mauvaise idée mais ne permet pas autant de flexibilité, comme je peux:

stop && build && test && deploy && start 

ou je peux juste:

stop && start

Ou n'importe quel nombre d'autres combinaisons qui pollueraient rapidement un script avec des grappes de drapeaux.

Même si vous écrivez un script, vous êtes susceptible de l'intégrer à d'autres scripts avec &&.

13
Bill K

La combinaison de commandes sous Linux peut être très utile.

Un bon exemple peut être le redémarrage d'une interface réseau distante via ssh (si vous avez changé la configuration réseau ou quelque chose ...).

ifdown eth0 && ifup eth0

Cela peut vous empêcher de vous rendre physiquement sur le serveur afin d'afficher l'interface vers laquelle vous étiez à l'origine. (Vous ne pourrez pas exécuter ifup eth0 si ifdown eth0 est exécuté seul.)

10
pymym213

Que font les commandes?

Afin de comprendre le pourquoi, nous devons également comprendre ce qui se fait. Les scripts sont séquentiels. Dans votre dernier exemple:

mkdir build
cd build
touch blank.txt

cd build sera exécuté indépendamment de mkdir build réussi ou non. Bien sûr si mkdir fails, vous verrez une erreur de cd.

mkdir build; cd build; touch blank.txt est un liste séquentielle . Cela peut être considéré comme essentiellement identique à plusieurs lignes de script. Shell Grammar traitera cela légèrement différemment, mais le résultat sera exactement le même que ci-dessus. Encore une fois, les commandes sont exécutées, que la précédente ait réussi ou non.

Enfin, il y a

mkdir build && cd build && touch blank.txt

qui est un ET listes - une séquence d'un ou plusieurs pipelines (ou commandes) séparés par && opérateur. Les commandes de cette liste sont exécutées avec l'associativité gauche. Cela signifie que le shell continuera de prendre deux commandes/pipelines séparés par &&, exécutez-en un à gauche en premier et exécutez celui à droite uniquement si celui de gauche réussit (retourne l'état de sortie zéro).

Dans cet exemple, que se passera-t-il?

  • Shell exécute mkdir build première.
  • la commande ci-dessus a réussi (code de sortie retourné 0), cd build s'exécutera
  • Encore une fois, regardons l'associativité de gauche. ... && touch blank.txt. A fait des trucs à gauche de && réussir? Si oui, exécutez touch blank.txt.

Pourquoi utiliser des listes séquentielles vs une liste AND?

La liste séquentielle mkdir build; cd build; touch blank.txt est logique lorsque nous sommes OK avec une des commandes qui échoue. Supposons que mkdir build existe déjà. Nous voulons toujours cd dans le build/ répertoire et touch blank.txt. Bien sûr, l'inconvénient ici est qu'il y a possibilité de résultat inattendu. Que faire si build n'a pas de bit d'exécution défini, et c'est pourquoi cd build échoue ? Le touch blank.txt se produira dans notre répertoire de travail actuel au lieu de _ build/.

Considérez maintenant mkdir build && cd build && touch blank.txt. C'est un peu plus logique, mais il a ses propres pièges. Si build existe déjà, il est probable que quelqu'un a déjà fait le touch blank.txt aussi, donc nous ne voulons peut-être pas faire touch blank.txt à nouveau car cela modifiera l'horodatage d'accès, ce qui n'est peut-être pas souhaitable.

Conclusion

Placer des commandes sur la même ligne dépend du but des commandes et de ce que vous essayez de réaliser. Les listes séquentielles peuvent simplifier la sortie, car Shell ne réaffichera pas l'invite tant que les commandes ne seront pas terminées. La liste ET permet l'exécution conditionnelle de commandes et peut empêcher l'exécution de commandes inutiles. L'essentiel, c'est que l'utilisateur a besoin de connaître les différences et ce qu'il doit choisir comme bon outil pour la tâche.

7

Une autre raison pourrait être dans la façon dont le script a vu le jour: il n'est pas inhabituel pour les utilisateurs techniques de créer de très longues lignes de commande interactives, de les étendre et de les tester à plusieurs reprises jusqu'à ce qu'ils fonctionnent comme souhaité, puis de coller le résultat dans un fichier texte et de gifler un #!/bin/bash En-tête dessus. Parfois, plusieurs segments d'un script sont développés par cette méthode.

Les constructions longues d'une ligne commençant par des chaînes for i in, grep | cut | sed | xargs, Une utilisation intensive des constructions && Et ||, $(cat something) sont souvent révélatrices de ces origines.

5
rackandboneman

Pourquoi nous aimons parfois l'utiliser

Lorsque nous écrivons des réponses ici dans Ask Ubunt il est utile de les mettre sur une seule ligne que les gens peuvent couper et coller dans leur terminal plus facilement. Par exemple:

$ CurrDir=$PWD ; cd /bin ; ls -la dd ; cd "$CurrDir"

-rwxr-xr-x 1 root root 72632 Mar  2  2017 dd

Comme mentionné précédemment, vous pouvez utiliser && pour le même effet, mais s'il y avait des erreurs ci-dessus, vous pourriez vous retrouver dans le mauvais répertoire. Dans ce cas ; est préférable à && dont le succès n'est pas testé:

rick@alien:~/askubuntu$ CurrDir=$PWD && cd /bin && llllssss -la dd && cd "$CurrDir"

llllssss: command not found

rick@alien:/bin$ 

Pourquoi nous devons l'utiliser

Il existe certaines commandes où vous devez enchaîner plusieurs lignes. Un cas est l'abréviation if- commandes -fi qui doit être une ligne. Par exemple:

$ [[ 2 -eq 2 ]] && { echo 2 equals 2 ; echo setting three to 3 ; three=3 ; }

2 equals 2
setting three to 3
4
WinEunuuchs2Unix

Y a-t-il des avantages techniques à tout entasser sur une seule ligne?

Ce n'est qu'une question de style. Il n'y a pas d'avantages techniques.

Je pense que d'autres ont très bien expliqué quelles sont les différences entre les trois lignes, mais notez que cela ne signifie pas que vous devez les avoir sur une seule ligne.

foo &&
bar

fait la même chose que foo && bar. Vous devez toujours être prudent, car

foo
&& bar

ne fait pas la même chose, mais exécute foo et produit ensuite une erreur de syntaxe. La règle générale dans la plupart des cas: lorsque le Shell sait par la syntaxe que votre ligne n'est pas complète, il vous permet de continuer sur la ligne suivante.
Lorsque la première ligne peut être exécutée telle quelle, alors elle sera exécutée (et la ligne suivante peut ou non être une erreur de syntaxe, car la première est manquante).

Lorsque vous voulez toujours que votre script soit plus lisible, vous pouvez échapper au nouveau caractère de ligne comme celui-ci

foo \
&& bar

Assurez-vous qu'il n'y a pas d'espace blanc après le \, car il doit précéder directement le caractère de nouvelle ligne.

3
allo

Beaucoup de réponses ici parlent de sécurité, par exemple, "Que se passe-t-il si cd échoue"?

Lors de la combinaison de commandes telles que cd foo && rm * résout ce problème, il existe une bien meilleure façon de le faire:

Prenez cette structure de dossiers:

.
./foo
./foo/bar
./foo2
./foo2/bar2
./test.sh
./do_not_delete_me

et le script suivant:

find .
cd notexisting
rm *
cd ../foo2
rm *
cd ..
find .

Comme notexisting n'existe pas, rm * s'exécutera dans . et supprimer do_not_delete_me.

Cependant, si vous démarrez le script avec set -e, il échouera avec la première commande échouée:

./test.sh: line 4: cd: notexisting: No such file or directory

Comme mentionné dans les commentaires, il est logique de l'inclure dans le Shebang: #!/bin/bash -e.

2
mrks