$ ls -l /tmp/test/my\ dir/ total 0
Mietin, miksi seuraavat tapat yllä olevan komennon suorittamiseen epäonnistuvat tai onnistuvat?
$ 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
Kommentit
- mywiki.wooledge.org/BashFAQ / 050
- Muuttujan lainaamisen unohtaminen bash / POSIX-kuoreissa – mutta entä jos…?
vastaus
Tätä on käsitelty useissa kysymyksissä unix.SE-sivustossa, yritän kerätä kaikki ongelmat, joita voin tässä käsitellä. Viitteet lopussa.
Miksi se epäonnistuu
Syy näiden ongelmien kohtaamiseen on sanan jakaminen ja se, että muuttujista laajennetut lainaukset eivät toimi lainausmerkeinä, vaan ovat vain tavallisia merkkejä.
kysymyksessä esitetyt tapaukset:
Tässä tehtävässä annetaan yksi merkkijono ls -l "/tmp/test/my dir"
– abc
:
$ abc="ls -l "/tmp/test/my dir""
Alla, $abc
on jaettu välilyöntiin, ja ls
saa kolme argumenttia -l
, "/tmp/test/my
ja dir"
(lainausmerkki toisen edessä ja toinen kolmannen takana). Vaihtoehto toimii, mutta polku käsitellään väärin:
$ $abc ls: cannot access ""/tmp/test/my": No such file or directory ls: cannot access "dir"": No such file or directory
Tässä laajennus noteerataan, joten sitä pidetään yhtenä sanana. Kuori yrittää löytää kirjaimellisesti ls -l "/tmp/test/my dir"
-niminen ohjelma, välilyönnit ja lainausmerkit mukaan lukien.
$ "$abc" bash: ls -l "/tmp/test/my dir": No such file or directory
Ja tässä, $abc
on jaettu ja vain ensimmäinen tuloksena oleva sana otetaan argumentiksi -c
, joten Bash suorittaa vain ls
nykyisessä hakemistossa. Muut sanat ovat argumentteja bash: lle, ja niitä käytetään $0
, $1
jne.
$ bash -c $abc "my dir"
Kanssa bash -c "$abc"
ja eval "$abc"
, siellä on lisäosa komentotulkin käsittelyvaihe, joka saa lainaukset toimimaan, mutta myös kaikki kuorilaajennukset käsitellään uudelleen , joten on olemassa riski, että suoritat vahingossa esim. komentojen korvaamisen käyttäjän toimittamista tiedoista, ellet olen hyvin huolellinen täynnä lainaamista.
Parempia tapoja tehdä se
Kaksi parempaa tapaa tallentaa komento ovat: a) käytä funktiota, b) käytä matriisimuuttujaa ( tai sijaintiparametrit).
Funktion käyttäminen:
Ilmoita yksinkertaisesti toiminto, jonka komento on sisällä, ja suorita toiminto ikään kuin se olisi komento. Funktion sisällä olevien komentojen laajennukset käsitellään vain, kun komento suoritetaan, ei kun se on määritelty, eikä sinun tarvitse lainata yksittäisiä komentoja.
# define it myls() { ls -l "/tmp/test/my dir" } # run it myls
Taulukon käyttäminen:
Taulukot mahdollistavat monisanaisten muuttujien luomisen, joissa yksittäiset sanat sisältävät valkoista tilaa. Tässä yksittäiset sanat tallennetaan erillisiksi taulukkoelementeiksi, ja "${array[@]}"
-laajennus laajentaa kutakin elementtiä erillisinä kuorisanoina:
# define the array mycmd=(ls -l "/tmp/test/my dir") # run the command "${mycmd[@]}"
Syntaksi on hieman kamala, mutta matriisien avulla voit myös rakentaa komentorivin kappaleittain. Esimerkiksi:
mycmd=(ls) # initial command if [ "$want_detail" = 1 ]; then mycmd+=(-l) # optional flag fi mycmd+=("$targetdir") # the filename "${mycmd[@]}"
tai pidä komentorivin osat vakiona ja käytä taulukon täyttöä vain osan siitä, kuten vaihtoehtoja tai tiedostonimiä:
options=(-x -v) files=(file1 "file name with whitespace") target=/somedir transmutate "${options[@]}" "${files[@]}" "$target"
Taulukoiden haittapuoli on, että ne eivät ole vakio-ominaisuus, joten tavalliset POSIX-kuoret (kuten dash
, oletusarvo /bin/sh
Debianissa / Ubuntussa) älä tue heitä (mutta katso alla). Bash, ksh ja zsh kuitenkin tekevät, joten todennäköisesti järjestelmässäsi on jokin kuori, joka tukee taulukoita.
Kuorissa, joissa nimettyjä taulukoita ei tueta, voidaan silti käyttää sijaintiparametreja (näennäisryhmä "$@"
) komennon argumenttien säilyttämiseksi.
Seuraavien tulee olla kannettavia komentobittejä, jotka vastaavat edellisen osan koodibittejä. Taulukko korvataan "$@"
, sijaintiparametrien luettelo. Asetus "$@"
tehdään set
-merkillä ja lainausmerkeillä. noin "$@"
ovat tärkeitä (nämä aiheuttavat luettelon elementtien lainaamisen erikseen).
Ensinnäkin yksinkertaisesti tallenna komento argumenteilla kansioon "$@"
ja suorita se:
set -- ls -l "/tmp/test/my dir" "$@"
Komennon komentorivivalintojen osien ehdollinen asettaminen:
set -- ls if [ "$want_detail" = 1 ]; then set -- "$@" -l fi set -- "$@" "$targetdir" "$@"
Käytä vain "$@"
vaihtoehdoille ja operandeille :
set -- -x -v set -- "$@" file1 "file name with whitespace" set -- "$@" /somedir transmutate "$@"
(Tietenkin "$@"
täytetään yleensä itse komentosarjan argumenteilla, joten sinä ” Sinun täytyy tallentaa ne jonnekin, ennen kuin määrität uudelleen "$@"
.)
Ole varovainen eval
kanssa !
Koska eval
ottaa käyttöön uuden tason tarjous- ja laajennuskäsittelyn, sinun on oltava varovainen käyttäjän syötteiden kanssa. Esimerkiksi tämä toimii niin kauan kuin käyttäjä ei kirjoita lainausmerkkejä:
read -r filename cmd="ls -l "$filename"" eval "$cmd";
Mutta jos ne antavat syötteen "$(uname)".txt
, komentosarjasi on onnellinen suorittaa komennon korvaamisen.
Matriisiversio on immuuni th: lle Koska sanat pidetään erillään koko ajan, filename
-sisältöä ei lainata tai muulla tavalla käsitellä.
read -r filename cmd=(ls -ld -- "$filename") "${cmd[@]}"
viitteet
- sanan jakaminen osassa BashGuide
- BashFAQ / 050 tai ” yritän laittaa komentoa muuttujassa, mutta monimutkaiset tapaukset epäonnistuvat aina! ”
- Kysymys Miksi komentotulkkini tukehtuu tyhjään tilaan tai muihin erikoismerkkeihin? , jossa käsitellään useita lainaamiseen ja tyhjään tilaan liittyviä kysymyksiä, mukaan lukien komentojen tallentaminen.
Kommentit
- voit kiertää eval-lainauksen tekemällä tekemällä
cmd="ls -l $(printf "%q" "$filename")"
. ei ole kaunis, mutta jos käyttäjä on kuollut asetettu käyttämääneval
, se auttaa. Se ’ on myös erittäin hyödyllinen komennon lähettämisessä samanlaisten asioiden, kutenssh foohost "ls -l $(printf "%q" "$filename")"
, tai tämän kysymyksen spritin kautta:ssh foohost "$cmd"
. - Ei ole suoraa yhteyttä, mutta oletko koodannut hakemiston kovasti? Siinä tapauksessa haluat ehkä tarkastella aliaksia. Jotain tällaista:
$ alias abc='ls -l "/tmp/test/my dir"'
Vastaa
Turvallisin tapa Suorita (ei-triviaali) komento on eval
. Sitten voit kirjoittaa komennon kuten tekisit komentoriville, ja se suoritetaan täsmälleen kuin olisit juuri kirjoittanut sen. Mutta sinun on lainattava kaikki.
Yksinkertainen tapaus:
abc="ls -l "/tmp/test/my dir"" eval "$abc"
ei niin yksinkertainen tapaus:
# command: awk "! a[$0]++ { print "foo: " $0; }" inputfile abc="awk "\""! a[$0]++ { print "foo: " $0; }"\"" inputfile" eval "$abc"
Kommentit
- Hei @HaukeLaging, mikä on \ ’ ’?
- @jumping_monkey Sinulla ei voi olla
'
'
-lainauksessa . Siksi sinun on (1) viimeisteltävä / keskeytettävä lainaus, (2) pakenemaan seuraava'
saadaksesi sen kirjaimellisesti, (3) kirjoittamalla kirjaimellinen lainaus ja (4) keskeytystapauksessa jatka lainattua merkkijonoa:(1)'(2)\(3)'(4)'
- Hei @HaukeLaging, mielenkiintoinen ja miksi ei vain
abc='awk \'! a[$0]++ { print "foo: " $0; }\' inputfile'
? - @jumping_monkey Puhumattakaan siitä, että selitin juuri sinulle, miksi se ei toimi: Eikö ’ ole järkevää testata koodia ennen sen lähettämistä?
vastaus
Toinen lainausmerkki rikkoo komennon.
Kun suoritan:
abc="ls -l "/home/wattana/Desktop"" $abc
Se antoi minulle virheen.
Mutta kun suoritan
abc="ls -l /home/wattana/Desktop" $abc
Virheessä ei ole lainkaan
Tätä ei voi korjata tällä hetkellä (minulle), mutta voit välttää virheen, jos hakemiston nimessä ei ole tilaa.
Tämä vastaus sanoi, että eval-komentoa voidaan käyttää tämän korjaamiseen, mutta se ei toimi minulle 🙁
kommentit
- Joo, se toimii niin kauan kuin ’ ei tarvita esim. tiedostonimet, joissa on upotettuja välilyöntejä (tai jotka sisältävät globaaleja merkkejä).
Vastaa
Jos tämä ei toimi div id = ”ce52beb0e5”>
, sinun tulee käyttää``
:
abc=`ls -l /tmp/test/my\ dir/`
Päivitä paremmin tapa:
abc=$(ls -l /tmp/test/my\ dir/)
Kommentit
- Tämä tallentaa komennon tuloksen muuttujaan. OP haluaa (oudolla tavalla) tallentaa komennon itse muuttujaan. Voi, ja sinun pitäisi todella alkaa käyttää
$( command... )
-tapahtumien sijaan. - Kiitos paljon selityksistä ja vinkeistä!
vastaus
Entä python3-yksilinjainen?
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 ..
Kommentit
- Kysymys koskee BASHia, ei pythonia.
Vastaa
Toinen yksinkertainen temppu minkä tahansa (triviaalin / ei-triviaalin) komennon suorittamiseen, joka on tallennettu tiedostoon abc
muuttuja on:
$ history -s $abc
ja paina UpArrow
tai Ctrl-p
tuoda se komentoriville. Toisin kuin millään muulla tavalla, tällä tavalla voit muokata sitä tarvittaessa ennen suorittamista.
Tämä komento lisää muuttujan sisällön uutena merkintänä Bash-historiaan ja voit kutsua sen UpArrow
.