$ ls -l /tmp/test/my\ dir/ total 0
Jeg lurte på hvorfor følgende måter å kjøre kommandoen over 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
- Sikkerhetsimplikasjoner av å glemme å sitere en variabel i bash / POSIX-skall – Men hva om …?
Svar
Dette har blitt diskutert i en rekke spørsmål om unix.SE, jeg vil prøve å samle alle problemer jeg kan komme på her. Referanser på slutten.
Hvorfor det mislykkes
Årsaken til at du møter disse problemene er splitting av ord og det faktum at anførselstegn utvidet fra variabler ikke fungerer som anførselstegn, men bare er vanlige tegn.
tilfeller presentert i spørsmålet:
Oppgaven her tildeler enkeltstrengen ls -l "/tmp/test/my dir"
til abc
:
$ abc="ls -l "/tmp/test/my dir""
Under, $abc
er delt på hvitt mellomrom, og ls
får de tre argumentene -l
, "/tmp/test/my
og dir"
(med et sitat foran på det andre og et annet på baksiden av det tredje). Alternativet fungerer, men banen blir feil behandlet:
$ $abc ls: cannot access ""/tmp/test/my": No such file or directory ls: cannot access "dir"": No such file or directory
Her siteres utvidelsen, så den holdes som et enkelt ord. Skallet prøver for å finne et program bokstavelig talt kalt ls -l "/tmp/test/my dir"
, mellomrom og sitater inkludert.
$ "$abc" bash: ls -l "/tmp/test/my dir": No such file or directory
Og her, $abc
er delt, og bare det første resulterende ordet blir tatt som argumentet til -c
, så Bash kjører bare ls
i den gjeldende katalogen. De andre ordene er argumenter for å bash, og brukes til å fylle $0
, $1
osv.
$ bash -c $abc "my dir"
Med bash -c "$abc"
og eval "$abc"
er det en ekstra trinn for skallbehandling, noe som gjør at anførselstegnene fungerer, men fører også til at alle skallutvidelser behandles på nytt , så det er en risiko for at du ved et uhell kjører, f.eks. en kommandosubstitusjon fra brukeroppgitte data, med mindre du » er veldig forsiktig full om å sitere.
Bedre måter å gjøre det på
De to bedre måtene å lagre en kommando på er a) bruk en funksjon i stedet, b) bruk en arrayvariabel ( eller posisjonsparametrene).
Bruk av en funksjon:
Bare erklær en funksjon med kommandoen inne, og kjør funksjonen som om den var en kommando. Utvidelser i kommandoer i funksjonen behandles bare når kommandoen kjører, ikke når den er definert, og du ikke trenger å sitere de enkelte kommandoene.
# define it myls() { ls -l "/tmp/test/my dir" } # run it myls
Ved hjelp av en matrise:
Arrays tillater å opprette flere ordvariabler der de enkelte ordene inneholder hvite rom. Her lagres de enkelte ordene som forskjellige matriseelementer, og "${array[@]}"
utvidelsen utvider hvert element som separate skallord:
# define the array mycmd=(ls -l "/tmp/test/my dir") # run the command "${mycmd[@]}"
Syntaksen er litt forferdelig, men arrays lar deg også 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 deler av kommandolinjen konstant og bruk matrisen til å fylle bare en del av den, som alternativer eller filnavn:
options=(-x -v) files=(file1 "file name with whitespace") target=/somedir transmutate "${options[@]}" "${files[@]}" "$target"
Ulempen med matriser er at de ikke er en standardfunksjon, så vanlige POSIX-skall (som dash
, standard /bin/sh
i Debian / Ubuntu) støtter dem ikke (men se nedenfor). Bash, ksh og zsh gjør det imidlertid, så det er sannsynlig at systemet har noe skall som støtter matriser.
Bruk av "$@"
I skjell uten støtte for navngitte matriser, kan man fremdeles bruke posisjonsparametrene (pseudo-array "$@"
) for å holde argumentene til en kommando.
Følgende skal være bærbare skriptbiter som tilsvarer kodebitene i forrige avsnitt. Matrisen erstattes med "$@"
, listen over posisjonsparametere. Innstilling av "$@"
gjøres med set
, og doble anførselstegn rundt "$@"
er viktige (disse fører til at elementene i listen siteres individuelt).
Først lagrer du bare en kommando med argumenter i "$@"
og kjører den:
set -- ls -l "/tmp/test/my dir" "$@"
Innstilling av deler av kommandolinjealternativene for en kommando betinget:
set -- ls if [ "$want_detail" = 1 ]; then set -- "$@" -l fi set -- "$@" "$targetdir" "$@"
Bruk bare "$@"
for alternativer og operander :
set -- -x -v set -- "$@" file1 "file name with whitespace" set -- "$@" /somedir transmutate "$@"
(Selvfølgelig er "$@"
vanligvis fylt med argumentene til selve skriptet, så du » Jeg må lagre dem et sted før du foretar en ny hensikt "$@"
.)
Vær forsiktig med eval
!
Da eval
introduserer et ekstra nivå på tilbud og utvidelsesbehandling, må du være forsiktig med brukerinntasting. For eksempel fungerer dette så lenge brukeren skriver ikke inn noen enkelt anførselstegn:
read -r filename cmd="ls -l "$filename"" eval "$cmd";
Men hvis de gir inngangen "$(uname)".txt
, vil skriptet ditt lykkelig kjører kommandosubstitusjonen.
En versjon med arrays er immun mot th siden ordene holdes atskilt for hele tiden, er det ikke noe sitat eller annen behandling for innholdet i filename
.
read -r filename cmd=(ls -ld -- "$filename") "${cmd[@]}"
Referanser
- Word Splitting i BashGuide
- BashFAQ / 050 eller » Jeg prøver å sette en kommando i en variabel, men de komplekse sakene mislykkes alltid! »
- Spørsmålet Hvorfor skjellskriptet mitt kveler på hvitt mellomrom eller andre spesialtegn? , som diskuterer en rekke spørsmål knyttet til sitering og hvitt mellomrom, inkludert lagring av kommandoer.
Kommentarer
- du kan komme deg rundt eval sitat ved å gjøre
cmd="ls -l $(printf "%q" "$filename")"
. ikke pen, men hvis brukeren er død på å bruke eneval
, hjelper det. Det ‘ er også veldig nyttig for å sende kommandoen gjennom lignende ting, for eksempelssh foohost "ls -l $(printf "%q" "$filename")"
, eller i sprit av dette spørsmålet:ssh foohost "$cmd"
. - Ikke direkte relatert, men har du hardkodet katalogen? I så fall vil du kanskje se på alias. Noe som:
$ alias abc='ls -l "/tmp/test/my dir"'
Svar
Den tryggeste måten å kjør en (ikke-triviell) kommando er eval
. Deretter kan du skrive kommandoen som du ville gjort på kommandolinjen, og den blir utført akkurat som om du nettopp hadde angitt den. Men du må sitere alt.
Enkelt tilfelle:
abc="ls -l "/tmp/test/my dir"" eval "$abc"
ikke så enkelt tilfelle:
# command: awk "! a[$0]++ { print "foo: " $0; }" inputfile abc="awk "\""! a[$0]++ { print "foo: " $0; }"\"" inputfile" eval "$abc"
Kommentarer
- Hei @HaukeLaging, hva er \ ‘ ‘?
- @jumping_monkey Du kan ikke ha en
'
i en'
-notert streng . Dermed må du (1) fullføre / avbryte sitatet, (2) unnslippe neste'
for å få det bokstavelig, (3) skriv det bokstavelige sitatet og (4) i tilfelle avbrudd, gjenoppta den siterte strengen:(1)'(2)\(3)'(4)'
- Hei @HaukeLaging, interessant, og hvorfor ikke bare
abc='awk \'! a[$0]++ { print "foo: " $0; }\' inputfile'
? - @jumping_monkey Hva si at jeg nettopp forklarte deg hvorfor det ikke fungerer: Ville det ikke ‘ være fornuftig å teste koden før du postet den? li>
Svar
Det andre sitattegnet bryter kommandoen.
Når jeg løper:
abc="ls -l "/home/wattana/Desktop"" $abc
Det ga meg en feil.
Men når jeg løper
abc="ls -l /home/wattana/Desktop" $abc
Det er ingen feil i det hele tatt
Det er ingen måte å fikse dette på den tiden (for meg), men du kan unngå feilen ved ikke å ha plass i katalognavnet.
Dette svaret sa at eval-kommandoen kan brukes til å fikse dette, men det fungerer ikke for meg 🙁
Kommentarer
- Ja, det fungerer så lenge det ‘ ikke er behov for f.eks. filnavn med innebygde mellomrom (eller de som inneholder glob-tegn).
Svar
Hvis dette ikke fungerer med ""
, så bør du bruke ``
:
abc=`ls -l /tmp/test/my\ dir/`
Oppdater bedre måte:
abc=$(ls -l /tmp/test/my\ dir/)
Kommentarer
- Dette lagrer resultatet av kommandoen i en variabel. OPEN ønsker (merkelig) å lagre selve kommandoen i en variabel. Åh, og du bør virkelig begynne å bruke
$( command... )
i stedet for backticks. - Tusen takk for avklaring og tips!
Svar
Hva 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ørsmålet handler om BASH, ikke python.
Svar
Et annet enkelt triks for å kjøre en hvilken som helst (triviell / ikke-triviell) kommando lagret i abc
variabel er:
$ history -s $abc
og trykk UpArrow
eller Ctrl-p
for å bringe den i kommandolinjen. I motsetning til andre metoder på denne måten kan du redigere den før kjøring hvis nødvendig.
Denne kommandoen vil legge til variabelt innhold som en ny oppføring i Bash-historikken, og du kan huske det ved å UpArrow
.