Hvordan iterere over argumenter i et Bash-skript
Jeg har en kompleks kommando som jeg ønsker å lage et shell/bash-skript av. Jeg kan skrive det i form av $ 1
enkelt:
foo $1 args -o $1.ext
Jeg vil være i stand til å sende flere inndatanavn til skriptet. Hva er den riktige måten å gjøre det på?
Og selvfølgelig vil jeg håndtere filnavn med mellomrom i dem.
830
3
Bruk
"$@"
for å representere alle argumentene:Dette vil iterere over hvert argument og skrive det ut på en egen linje. $@ oppfører seg som $* bortsett fra at når det er sitert, blir argumentene delt opp riktig hvis det er mellomrom i dem:
Omskriving av et nå slettet svar av VonC._ Robert Gamble&# 39s kortfattede svar omhandler direkte spørsmålet. Denne utdyper noen problemer med filnavn som inneholder mellomrom. Se også: ${1:+"$@"} i /bin/sh Grunnleggende tese:
"$@"
er riktig, og$*
(uten anførselstegn) er nesten alltid feil. Dette er fordi"$@"
fungerer fint når argumentene inneholder mellomrom, og fungerer på samme måte som$*
når de ikke gjør det. I noen tilfeller er"$*"
også OK, men"$@"
fungerer vanligvis (men ikke alltid) på de samme stedene. alltid) fungerer på de samme stedene. Uten anførselstegn er$@
og$*
likeverdige (og nesten alltid feil). Så hva er forskjellen mellom$*
,$@
,"$*"
og"$@"
? De er alle relatert til 'alle argumentene til skallet', men de gjør forskjellige ting. Når de ikke er sitert, gjør$*
og$@
det samme. De behandler hvert 'ord' (sekvens av ikke-whitespace) som et eget argument. De siterte formene er imidlertid ganske forskjellige:"$*"
behandler argumentlisten som en enkelt mellomromseparert streng, mens"$@"
behandler argumentene nesten nøyaktig slik de var da de ble spesifisert på kommandolinjen."$@"
ekspanderer til ingenting i det hele tatt når det ikke er noen posisjonsargumenter;"$*"
ekspanderer til en tom streng; og ja, det er en forskjell, selv om det kan være vanskelig å oppfatte det. Se mer informasjon nedenfor, etter introduksjonen av (ikke-standard) kommandoenal
. Sekundær avhandling: hvis du trenger å behandle argumenter med mellomrom og deretter deretter sende dem videre til andre kommandoer, trenger du noen ganger ikke-standardiserte verktøy for å hjelpe deg. (Eller du bør bruke arrays, forsiktig:"${array[@]}"
oppfører seg analogt med"$@"
). Eksempel:Hvorfor fungerer det ikke? Det fungerer ikke fordi skallet behandler anførselstegn før det ekspanderer variabler. Så, for å få skallet til å ta hensyn til anførselstegnene innebygd i
$var
, må du brukeeval
:Dette blir virkelig vanskelig når du har filnavn som " `Han sa, "Don't do this!"" (med anførselstegn og doble anførselstegn og mellomrom).
Skallene (alle sammen) gjør det ikke spesielt enkelt å håndtere slikt ting, så (morsomt nok) mange Unix-programmer gjør ikke en god jobb med å håndtere dem håndtere dem. På Unix kan et filnavn (enkeltkomponent) inneholde alle tegn unntatt skråstrek og NUL
'\0'
. Imidlertid oppmuntrer skallene sterkt til ingen mellomrom eller nye linjer eller tabulatorer hvor som helst i et banenavn. Det er også grunnen til at standard Unix-filnavn ikke inneholder mellomrom osv. Når du arbeider med filnavn som kan inneholde mellomrom og annet plagsomme tegn, må du være ekstremt forsiktig, og jeg fant for lenge siden ut at jeg trengte et program som lenge siden at jeg trengte et program som ikke er standard på Unix. Jeg kaller detescape
(versjon 1.1 ble datert 1989-08-23T16:01:45Z). Her er et eksempel påescape
i bruk - med SCCS-kontrollsystemet. Det er et cover-skript som gjør både endelta
(tenk check-in) og enget
(tenk check-out). Forskjellige argumenter, spesielt-y
(grunnen til at du gjorde endringen) vil inneholde blanktegn og nye linjer. Merk at skriptet er fra 1992, så det bruker back-ticks i stedet for$(cmd ...)
notasjon og bruker ikke#!/bin/sh
på første linje.(Jeg ville sannsynligvis ikke bruke escape fullt så grundig i disse dager - det er ikke nødvendig med
-e
argumentet, for eksempel - men totalt sett er dette et av mine enklere et av mine enklere skript som brukerescape
). Programmetescape
sender ganske enkelt ut argumentene sine, ganske liktecho``. gjør, men det sørger for at argumentene er beskyttet for bruk med
eval(ett nivå av
eval; jeg har et program som gjorde ekstern shell-kjøring, og som trengte å unnslippe kjøring, og som trengte å unnslippe utdataene fra
escape`).Jeg har et annet program som heter
al
som lister opp argumentene sine ett per linje (og det er enda eldre: versjon 1.1 datert 1987-01-27T14:35:49). Det er mest nyttig ved feilsøking av skript, da det kan plugges inn i en kommandolinje for å se hvilke argumenter som faktisk sendes til kommandoen.[ Lagt til: Og nå for å vise forskjellen mellom de forskjellige
"$@"
notasjonene, her er et eksempel til:Legg merke til at ingenting bevarer de opprinnelige mellomrommene mellom
*
og*/*
på kommandolinjen. Legg også merke til at du kan endre 'kommandolinjeargumentene' i skallet ved å bruke:Dette angir 4 alternativer, '
-new
', '-opt
', 'and
', og 'arg med mellomrom
'. &#;br> ] Hmm, det er et ganske langt svar - kanskje eksegese er et bedre begrep. Kildekode forescape
tilgjengelig på forespørsel (e-post til fornavn dot etternavn at gmail dot com). Kildekoden foral
er utrolig enkel:Det er alt. Det tilsvarer
test.sh
-skriptet som Robert Gamble viste, og kan skrives som en shell-funksjon (men shell-funksjoner eksisterte ikke i den lokale versjonen av Bourne-shell da jeg først skreval
). Legg også merke til at du kan skriveal
som et enkelt shell-skript:Det betingede er nødvendig slik at det ikke produserer noen utdata når det ikke sendes noen argumenter. Kommandoen
printf
vil produsere en tom linje med bare formatstrengen som argument, men C-programmet produserer ingenting.Legg merke til at Roberts svar er riktig, og det fungerer også i
sh
. Du kan (portabelt) forenkle det ytterligere:er ekvivalent med:
Det vil si at du ikke trenger noe!
Testing (
$
er ledetekst):Jeg leste først om dette i Unix Programming Environment av Kernighan og Pike.
I
bash
dokumentererhelp for
dette: