Kuinka voimme suorittaa muuttujaan tallennetun komennon?

$ 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

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

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ään eval, se auttaa. Se ’ on myös erittäin hyödyllinen komennon lähettämisessä samanlaisten asioiden, kuten ssh 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.

Vastaa

Sähköpostiosoitettasi ei julkaista. Pakolliset kentät on merkitty *