$ ls -l /tmp/test/my\ dir/ total 0
Jeg undrede mig over, hvorfor følgende måder at køre ovenstående kommando mislykkes eller lykkes på?
$ abc="ls -l "/tmp/test/my dir"" $ $abc ls: cannot access ""/tmp/test/my": No such file or directory ls: cannot access "dir"": No such file or directory $ "$abc" bash: ls -l "/tmp/test/my dir": No such file or directory $ bash -c $abc "my dir" $ bash -c "$abc" total 0 $ eval $abc total 0 $ eval "$abc" total 0
Kommentarer
- mywiki.wooledge.org/BashFAQ / 050
- Sikkerhedsimplikationer ved at glemme at citere en variabel i bash / POSIX-skaller – Men hvad hvis …?
Svar
Dette er blevet diskuteret i en række spørgsmål om unix.SE, jeg prøver at samle alle spørgsmål, jeg kan komme med her. Referencer i slutningen.
Hvorfor det mislykkes
Årsagen til at du står over for disse problemer er opdeling af ord og det faktum, at citater udvidet fra variabler fungerer ikke som citater, men er bare almindelige tegn.
tilfælde præsenteret i spørgsmålet:
Opgaven tildeler her den enkelte streng ls -l "/tmp/test/my dir"
til abc
:
$ abc="ls -l "/tmp/test/my dir""
Nedenfor $abc
deles i det hvide rum, og ls
får de tre argumenter -l
, "/tmp/test/my
og dir"
(med et citat på forsiden af det andet og et andet på bagsiden af det tredje). Indstillingen fungerer, men stien behandles forkert:
$ $abc ls: cannot access ""/tmp/test/my": No such file or directory ls: cannot access "dir"": No such file or directory
Her citeres udvidelsen, så den holdes som et enkelt ord. Skallen prøver for at finde et program bogstaveligt talt kaldet ls -l "/tmp/test/my dir"
, mellemrum og citater inkluderet.
$ "$abc" bash: ls -l "/tmp/test/my dir": No such file or directory
Og her $abc
deles, og kun det første resulterende ord tages som argumentet til -c
, så Bash kører bare ls
i det aktuelle bibliotek. De andre ord er argumenter, der skal bash, og bruges til at udfylde $0
, $1
osv.
$ bash -c $abc "my dir"
Med bash -c "$abc"
og eval "$abc"
er der en ekstra shell-behandlingstrin, hvilket får tilbudene til at fungere, men får også alle shell-udvidelser til at blive behandlet igen så der er “en risiko for ved et uheld at køre f.eks. en kommandosubstitution fra bruger-tilvejebragte data, medmindre du” er meget opmærksom fulde om citering.
Bedre måder at gøre det på
De to bedre måder at gemme en kommando på er: a) brug en funktion i stedet, b) brug en matrixvariabel ( eller positionsparametrene).
Brug af en funktion:
Bare erklær en funktion med kommandoen inde, og kør funktionen som om den var en kommando. Udvidelser i kommandoer inden for funktionen behandles kun, når kommandoen kører, ikke når den er defineret, og du ikke behøver at citere de enkelte kommandoer.
# define it myls() { ls -l "/tmp/test/my dir" } # run it myls
Brug af en matrix:
Arrays tillader oprettelse af flere ordvariabler, hvor de enkelte ord indeholder hvide plads. Her lagres de enkelte ord som forskellige matrixelementer, og udvidelsen "${array[@]}"
udvider hvert element som separate shell-ord:
# define the array mycmd=(ls -l "/tmp/test/my dir") # run the command "${mycmd[@]}"
Syntaksen er lidt forfærdelig, men arrays giver dig også mulighed for at bygge kommandolinjen stykke for stykke. For eksempel:
mycmd=(ls) # initial command if [ "$want_detail" = 1 ]; then mycmd+=(-l) # optional flag fi mycmd+=("$targetdir") # the filename "${mycmd[@]}"
eller hold dele af kommandolinjen konstant, og brug arrayet til kun at udfylde en del af det, som valgmuligheder eller filnavne:
options=(-x -v) files=(file1 "file name with whitespace") target=/somedir transmutate "${options[@]}" "${files[@]}" "$target"
Ulempen ved arrays er, at de ikke er en standardfunktion, så almindelige POSIX-skaller (som dash
, standard /bin/sh
i Debian / Ubuntu) understøtter dem ikke (men se nedenfor). Bash, ksh og zsh gør det dog, så det er sandsynligt, at dit system har en skal, der understøtter arrays.
Brug af "$@"
I skaller uden understøttelse af navngivne arrays kan man stadig bruge positionsparametrene (pseudo-array "$@"
) for at holde argumenterne for en kommando.
Følgende skal være bærbare scriptbits, der svarer til kodebitene i det foregående afsnit. Arrayet erstattes med "$@"
, listen over positionsparametre. Indstilling af "$@"
udføres med set
, og de dobbelte citater omkring "$@"
er vigtige (disse får elementerne på listen til at blive citeret individuelt).
Først skal du blot gemme en kommando med argumenter i "$@"
og køre den:
set -- ls -l "/tmp/test/my dir" "$@"
Betinget indstilling af dele af kommandolinjemulighederne for en kommando:
set -- ls if [ "$want_detail" = 1 ]; then set -- "$@" -l fi set -- "$@" "$targetdir" "$@"
Brug kun "$@"
til indstillinger og operander :
set -- -x -v set -- "$@" file1 "file name with whitespace" set -- "$@" /somedir transmutate "$@"
(Selvfølgelig er "$@"
normalt fyldt med argumenterne til selve scriptet, så du ” Jeg bliver nødt til at gemme dem et eller andet sted, før du genindleder "$@"
.)
Vær forsigtig med eval
!
Da eval
introducerer et ekstra niveau af citat- og udvidelsesbehandling, skal du være forsigtig med brugerinput. For eksempel fungerer dette, så længe brugeren indtaster ikke nogen enkelt anførselstegn:
read -r filename cmd="ls -l "$filename"" eval "$cmd";
Men hvis de giver input "$(uname)".txt
, vil dit script med glæde kører kommandosubstitutionen.
En version med arrays er immun over for th da ordene holdes adskilt hele tiden, er der intet citat eller anden behandling for indholdet af filename
.
read -r filename cmd=(ls -ld -- "$filename") "${cmd[@]}"
Referencer
- Word Splitting i BashGuide
- BashFAQ / 050 eller ” Jeg prøver at placere en kommando i en variabel, men de komplekse tilfælde mislykkes altid! ”
- Spørgsmålet Hvorfor mit shell-script choker på hvidt mellemrum eller andre specialtegn? , som diskuterer en række problemer relateret til citering og hvidt mellemrum, herunder lagring af kommandoer.
Kommentarer
- du kan omgå det evale citat ved at gøre
cmd="ls -l $(printf "%q" "$filename")"
. ikke smuk, men hvis brugeren er død ved at bruge eneval
, hjælper det. Det ‘ er også meget nyttigt til at sende kommandoen gennem lignende ting, såsomssh foohost "ls -l $(printf "%q" "$filename")"
, eller i sprit af dette spørgsmål:ssh foohost "$cmd"
. - Ikke direkte relateret, men har du hardkodet biblioteket? I så fald vil du måske se på alias. Noget som:
$ alias abc='ls -l "/tmp/test/my dir"'
Svar
Den sikreste måde at kør en (ikke-triviel) kommando er eval
. Derefter kan du skrive kommandoen, som du ville gøre på kommandolinjen, og den udføres nøjagtigt som om du lige havde indtastet den. Men du skal citere alt.
Enkel sag:
abc="ls -l "/tmp/test/my dir"" eval "$abc"
ikke så enkel sag:
# command: awk "! a[$0]++ { print "foo: " $0; }" inputfile abc="awk "\""! a[$0]++ { print "foo: " $0; }"\"" inputfile" eval "$abc"
Kommentarer
- Hej @HaukeLaging, hvad er \ ‘ ‘?
- @jumping_monkey Du kan ikke have en
'
i en'
-citeret streng . Således skal du (1) afslutte / afbryde citatet, (2) undslippe det næste'
for at få det bogstaveligt, (3) skriv det bogstavelige citat og (4) i tilfælde af afbrydelse genoptag den citerede streng:(1)'(2)\(3)'(4)'
- Hej @HaukeLaging, interessant, og hvorfor ikke bare
abc='awk \'! a[$0]++ { print "foo: " $0; }\' inputfile'
? - @jumping_monkey Lad være med at jeg lige har forklaret dig, hvorfor det ikke virker: Ville det ikke være ‘, at teste koden, før du postede den?
Svar
Det andet citattegn bryder kommandoen.
Når jeg kører:
abc="ls -l "/home/wattana/Desktop"" $abc
Det gav mig en fejl.
Men når jeg kører
abc="ls -l /home/wattana/Desktop" $abc
Der er slet ingen fejl
Der er ingen måde at løse dette på det tidspunkt (for mig), men du kan undgå fejlen ved ikke at have plads i katalognavnet.
Dette svar sagde, at eval-kommandoen kan bruges til at rette dette, men det virker ikke for mig 🙁
Kommentarer
- Ja, det fungerer, så længe der ‘ ikke er behov for f.eks. filnavne med indlejrede mellemrum (eller dem, der indeholder glob-tegn).
Svar
Hvis dette ikke fungerer med ""
, så skal du bruge ``
:
abc=`ls -l /tmp/test/my\ dir/`
Opdater bedre måde:
abc=$(ls -l /tmp/test/my\ dir/)
Kommentarer
- Dette gemmer resultatet af kommandoen i en variabel. OPen ønsker (underligt) at gemme selve kommandoen i en variabel. Åh, og du skal virkelig begynde at bruge
$( command... )
i stedet for backticks. - Mange tak for afklaringerne og tipene!
Svar
Hvad med en python3 one-liner?
bash-4.4# pwd /home bash-4.4# CUSTOM="ls -altr" bash-4.4# python3 -c "import subprocess; subprocess.call(\"$CUSTOM\", shell=True)" total 8 drwxr-xr-x 2 root root 4096 Mar 4 2019 . drwxr-xr-x 1 root root 4096 Nov 27 16:42 ..
Kommentarer
- Spørgsmålet handler om BASH, ikke python.
Svar
Et andet simpelt trick til at køre enhver (triviel / ikke-triviel) kommando gemt i abc
variabel er:
$ history -s $abc
og skub UpArrow
eller Ctrl-p
for at bringe det i kommandolinjen. I modsætning til andre metoder på denne måde kan du redigere det før udførelse, hvis det er nødvendigt.
Denne kommando tilføjer variabelt indhold som en ny post i Bash-historikken, og du kan huske det ved at UpArrow
.