Hoe itereren over argumenten in een Bash script
Ik heb een complex commando waar ik een shell/bash script van wil maken. Ik kan het gemakkelijk in termen van $1
schrijven:
foo $1 args -o $1.ext
Ik wil meerdere inputnamen aan het script kunnen doorgeven. Wat'is de juiste manier om dit te doen?
En, natuurlijk, wil ik bestandsnamen met spaties erin kunnen verwerken.
830
3
Gebruik
"$@"
om alle argumenten weer te geven:Dit zal over elk argument itereren en het op een aparte regel uitprinten. $@ gedraagt zich als $*, behalve dat wanneer de argumenten geciteerd worden, ze netjes opgesplitst worden als er spaties in staan:
Herschrijving van een nu verwijderd antwoord door VonC. Het beknopte antwoord van Robert Gamble's gaat direct in op de vraag. Dit antwoord gaat dieper in op enkele problemen met bestandsnamen die spaties bevatten. Zie ook: ${1:+"$@"} in /bin/sh Basis stelling:
"$@"
is correct, en$*
(ongeciteerd) is bijna altijd fout. Dit komt omdat"$@"
prima werkt als argumenten spaties bevatten, en hetzelfde werkt als$*
als ze geen spaties bevatten. In sommige omstandigheden,"$*"
is ook OK, maar"$@"
werkt meestal (maar niet altijd) op dezelfde plaatsen. Ongeciteerd,$@
en$*
zijn gelijkwaardig (en bijna altijd fout). Dus, wat is het verschil tussen$*
,$@
,"$*"
, en"$@"
? Ze zijn allemaal gerelateerd aan 'alle argumenten voor de shell', maar ze doen verschillende dingen. Als ze niet aangehaald worden, doen$*
en$@
hetzelfde. Ze behandelen elk 'woord' (opeenvolging van niet-whitespace) als een afzonderlijk argument. De geciteerde vormen zijn echter heel verschillend:"$*"
behandelt de argumentenlijst als een enkele door spaties gescheiden tekenreeks, terwijl"$@"
de argumenten bijna precies zo behandelt als ze waren toen ze op de opdrachtregel werden opgegeven."$@"
breidt zich uit tot helemaal niets als er geen positionele argumenten zijn;"$*"
breidt zich uit tot een lege string — en ja, er'is een verschil, hoewel het moeilijk kan zijn om het te zien. Zie meer informatie hieronder, na de introductie van het (niet-standaard) commandoal
. Tweede stelling: als je argumenten met spaties moet verwerken en dan doorgeven aan andere commando's, dan heb je soms niet-standaard hulpmiddelen nodig om te helpen. (Of je moet arrays gebruiken, voorzichtig:"${array[@]}"
gedraagt zich analoog aan"$@"
). Voorbeeld:Waarom werkt dat niet? Het werkt niet omdat de shell eerst aanhalingstekens verwerkt voordat het variabelen. Dus, om de shell aandacht te laten besteden aan de aanhalingstekens in
$var
, moet jeeval
gebruiken:Dit wordt echt lastig als je bestandsnamen hebt zoals "
He said, "Don't do this!"
" (met aanhalingstekens en dubbele aanhalingstekens en spaties).De shells (allemaal) maken het niet bijzonder gemakkelijk om met zulke dingen, dus (grappig genoeg) kunnen veel Unix programma's er niet goed mee ze te verwerken. Op Unix kan een bestandsnaam (enkele component) alle tekens bevatten behalve schuine streep en NUL
'\0'
. De shells raden echter sterk aan om geen spaties, nieuwe regels of tabs overal in een padnaam. Dit is ook de reden waarom standaard Unix bestandsnamen geen spaties, etc. bevatten. Wanneer je te maken hebt met bestandsnamen die spaties en andere lastige karakters bevatten, moet je uiterst voorzichtig zijn, en ik vond lang geleden dat ik een programma nodig had dat niet standaard is op Unix. Ik noem hetescape
(versie 1.1 dateert van 1989-08-23T16:01:45Z). Hier is een voorbeeld vanescape
in gebruik - met het SCCS besturingssysteem. Het is een cover script dat zowel eendelta
(denk check-in) als eenget
(denk check-out). Verschillende argumenten, vooral-y
(de reden waarom je de wijziging hebt gemaakt) zouden spaties en nieuwe regels bevatten. Merk op dat het script dateert uit 1992, dus het gebruikt back-ticks in plaats van$(cmd ...)
notatie en gebruikt niet#!/bin/sh
op de eerste regel.(Ik zou escape tegenwoordig waarschijnlijk niet meer zo grondig gebruiken - het is het is niet nodig met het
-e
argument, bijvoorbeeld - maar over het algemeen is dit een van mijn eenvoudigere scripts metescape
). Hetescape
programma voert simpelweg zijn argumenten uit, net zoalsecho
doet, maar het zorgt ervoor dat de argumenten beschermd zijn voor gebruik meteval
(één niveau vaneval
; ik heb een programma dat op afstand shell en dat moest ontsnappen aan de uitvoer vanescape
).Ik heb een ander programma,
al
, dat zijn argumenten één voor één opsomt (en het is nog ouder: versie 1.1 uit 1987-01-27T14:35:49). Het is vooral nuttig bij het debuggen van scripts, omdat het kan worden aangesloten op een commandoregel om te zien welke argumenten daadwerkelijk aan het commando worden doorgegeven.[Aangevuld: En om het verschil te laten zien tussen de verschillende
"$@"
notaties, volgt hier nog een voorbeeld:Merk op dat niets de originele spaties tussen de
*
en*/*
op de commandoregel behoudt. Merk ook op dat je de 'command line arguments' in de shell kunt veranderen door te gebruiken:Dit stelt 4 opties in, '
-new
', '-opt
', 'and
', en 'arg met spatie
'.] Hmm, dat's nogal een lang antwoord - misschien is exegese de betere term. Broncode voor
escape
beschikbaar op aanvraag (email naar firstname dot achternaam at gmail dot com). De broncode vooral
is ongelooflijk eenvoudig:Dat's alles. Het is gelijkwaardig aan het
test.sh
script dat Robert Gamble liet zien, en zou geschreven kunnen worden als een shell functie (maar shell functies bestonden nog niet in de lokale versie van Bourne shell toen ikal
voor het eerst schreef). Merk ook op dat jeal
kunt schrijven als een eenvoudig shell script:De voorwaarde is nodig om geen uitvoer te produceren als er geen argumenten worden doorgegeven. Het
printf
commando zal een lege regel produceren met alleen het format string argument, maar het C programma produceert niets.Merk op dat Robert's antwoord correct is, en het werkt ook in
sh
. Je kunt het (portabel) nog verder vereenvoudigen:is gelijkwaardig aan:
Ik bedoel, je hebt niets nodig!
Testen (
$
is commando prompt):Ik heb hier voor het eerst over gelezen in Unix Programming Environment van Kernighan en Pike.
In
bash
,help for
wordt dit gedocumenteerd: