Hogyan futtathatunk egy változóban tárolt parancsot?

$ ls -l /tmp/test/my\ dir/ total 0 

Kíváncsi voltam, miért nem sikerül vagy sikerül a fenti parancs futtatásának következő módjai?

$ 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 

Megjegyzések

Válasz

Ezt az unix.SE-n számos kérdés megvitatta, megpróbálom összegyűjteni az összes kérdések, amelyeket itt fel tudok találni. Hivatkozások a végén.


Miért nem sikerül

Az oka annak, hogy szembesül a problémákkal: szófelosztás és az a tény, hogy a változókból kibővített idézetek nem idézőjelekként működnek, hanem csak hétköznapi karakterek.

a kérdésben bemutatott esetek:

Az itt megadott hozzárendelés egyetlen sztringet rendel hozzá ls -l "/tmp/test/my dir"abc:

$ abc="ls -l "/tmp/test/my dir"" 

Lent, $abc fel van osztva a szóközzel, és ls megkapja a három argumentumot -l, "/tmp/test/my és dir" (a második elején egy idézettel, a harmadik hátulján pedig egy másikkal). Az opció működik, de az útvonal helytelenül kerül feldolgozásra:

$ $abc ls: cannot access ""/tmp/test/my": No such file or directory ls: cannot access "dir"": No such file or directory 

Itt a bővítmény idézett, tehát egyetlen szóként marad. A shell megpróbálja hogy megtalálja a szó szoros értelmében ls -l "/tmp/test/my dir" nevű programot, szóközökkel és idézőjelekkel.

$ "$abc" bash: ls -l "/tmp/test/my dir": No such file or directory 

És itt: $abc fel van osztva, és csak az első eredményül kapott szót vesszük argumentumként a -c argumentumnak, így Bash csak futtatja a ls az aktuális könyvtárban. A többi szó a bash argumentuma, és a $0, $1 stb.

$ bash -c $abc "my dir" 

bash -c "$abc" és eval "$abc" esetén további egy van shell feldolgozási lépés, amely az idézőjeleket működőképessé teszi, de emellett az összes shell kiterjesztést újra feldolgozza , így fennáll annak a veszélye, hogy véletlenül futtatunk például egy parancscserét a felhasználó által megadott adatokból, hacsak nem ön ” nagyon vigyázok az idézésről.


Jobb módszerek rá

A parancs kétféle jobb tárolási módja: a) használjon inkább egy függvényt, b) használjon egy tömb változót ( vagy a helyzeti paraméterek).

Funkció használata:

Egyszerűen deklarálja egy függvényt, amelynek belsejében van a parancs, és futtassa a függvényt, mintha parancs lenne. A függvényen belüli parancsok kiterjesztéseit csak akkor dolgozzuk fel, amikor a parancs fut, és nem akkor, amikor meg van határozva, és nem kell idéznie az egyes parancsokat.

# define it myls() { ls -l "/tmp/test/my dir" } # run it myls 

Tömb használata:

A tömbök lehetővé teszik többszavas változók létrehozását, ahol az egyes szavak fehéret tartalmaznak tér. Itt az egyes szavakat külön tömbelemekként tárolják, és a "${array[@]}" kiterjesztés az egyes elemeket külön shell szavakként bővíti ki:

# define the array mycmd=(ls -l "/tmp/test/my dir") # run the command "${mycmd[@]}" 

A szintaxis kissé szörnyű, de a tömbök lehetővé teszik a parancssor egyesével történő felépítését is. Például:

mycmd=(ls) # initial command if [ "$want_detail" = 1 ]; then mycmd+=(-l) # optional flag fi mycmd+=("$targetdir") # the filename "${mycmd[@]}" 

vagy tartsa állandóan a parancssor egyes részeit, és a tömb csak egy részét töltse ki, például opciókat vagy fájlneveket:

options=(-x -v) files=(file1 "file name with whitespace") target=/somedir transmutate "${options[@]}" "${files[@]}" "$target" 

A tömbök hátránya, hogy “nem szabványos funkciók, ezért a sima POSIX-héjak (például dash, az alapértelmezett) /bin/sh a Debian / Ubuntuban) nem támogatja őket (de lásd alább). A Bash, a ksh és a zsh azonban igen, ezért valószínűleg a rendszernek van olyan héja, amely támogatja a tömböket.

A

Azokban a héjakban, amelyek nem támogatják a megnevezett tömböket, továbbra is használhatók a helyzeti paraméterek (az áltömb "$@") a parancs argumentumainak megtartásához.

A következőknek olyan hordozható szkriptbiteknek kell lenniük, amelyek megegyeznek az előző szakasz kódbitjeivel. A tömböt "$@", a helyzeti paraméterek listája. A "$@" beállítása a set és a dupla idézőjelekkel történik. a "$@" körül fontos (ezek a lista elemeit külön-külön idézik).

Először egyszerűen argumentumokat tartalmazó parancsot kell tárolni a "$@" fájlban és futtatni:

set -- ls -l "/tmp/test/my dir" "$@" 

A parancs parancssori opcióinak feltételes beállítása:

set -- ls if [ "$want_detail" = 1 ]; then set -- "$@" -l fi set -- "$@" "$targetdir" "$@" 

Csak a "$@" parancsot használja opciókhoz és operandusokhoz :

set -- -x -v set -- "$@" file1 "file name with whitespace" set -- "$@" /somedir transmutate "$@" 

(Természetesen a "$@" általában magának a szkriptnek az argumentumaival van kitöltve, tehát Ön ” El kell mentenem őket valahová, mielőtt újratervezném a "$@".)


Legyen óvatos a eval !

Mivel a eval egy további szintet vezet be az árajánlatok és a bővítés feldolgozásában, körültekintően kell eljárnia a felhasználói bevitelsel. Például ez addig működik, amíg a felhasználó nem ír be egyetlen idézőjelet sem:

read -r filename cmd="ls -l "$filename"" eval "$cmd"; 

De ha megadják a bemenetet "$(uname)".txt, akkor a szkript boldogan futtatja a parancs behelyettesítését.

A tömbökkel ellátott verzió immúnis a th-re mivel mivel a szavakat egész idő alatt külön tartják, nincs árajánlat vagy egyéb feldolgozás a filename tartalmához.

read -r filename cmd=(ls -ld -- "$filename") "${cmd[@]}" 

Hivatkozások

Megjegyzések

  • megkerülheti az eval idéző dolgot a cmd="ls -l $(printf "%q" "$filename")" művelettel. nem szép, de ha a felhasználó halottan van beállítva egy eval használatával, az segít. ‘ nagyon hasznos a parancs elküldéséhez hasonló dolgokon keresztül is, például ssh foohost "ls -l $(printf "%q" "$filename")", vagy ennek a kérdésnek az írása alatt: ssh foohost "$cmd".
  • Nem közvetlenül kapcsolódik, de keményen kódolta a könyvtárat? Ebben az esetben érdemes álnevet nézni. Valami ilyesmi: $ alias abc='ls -l "/tmp/test/my dir"'

Válasz

A legbiztonságosabb módszer futtasson egy (nem triviális) parancsot: eval. Ezután a parancsot úgy írhatja, ahogyan a parancssorra tenné, és pontosan úgy hajtódik végre, mintha csak beírta volna. De mindent idéznie kell.

Egyszerű eset:

abc="ls -l "/tmp/test/my dir"" eval "$abc" 

nem olyan egyszerű eset:

# command: awk "! a[$0]++ { print "foo: " $0; }" inputfile abc="awk "\""! a[$0]++ { print "foo: " $0; }"\"" inputfile" eval "$abc" 

Megjegyzések

  • Szia @HaukeLaging, mi az a
  • @jumping_monkey ' nem lehet ' -jelölt karaktersorozaton belül . Így (1) be kell fejeznie / meg kell szakítania az idézetet, (2) el kell kerülnie a következő ' et annak érdekében, hogy szó szerint megkapja, (3) írja be a szó szerinti idézetet és (4) megszakítás esetén folytassa az idézett karakterláncot: (1)'(2)\(3)'(4)'
  • Szia @HaukeLaging, érdekes, és miért nem csak abc='awk \'! a[$0]++ { print "foo: " $0; }\' inputfile' ?
  • @jumping_monkey Nem is beszélve arról, hogy csak elmagyaráztam neked, hogy ez miért nem működik: Nem lenne ‘ értelme tesztelni a kódot, mielőtt elküldenéd?

Válasz

A második idézőjel megszakítja a parancsot.

Amikor futtatom:

abc="ls -l "/home/wattana/Desktop"" $abc 

Hibát adott.

De amikor futok

abc="ls -l /home/wattana/Desktop" $abc 

Egyáltalán nincs hiba

Ezt egyelőre nem lehet kijavítani (számomra), de elkerülheti a hibát, ha nincs hely a könyvtárnévben.

Ez a válasz azt mondta, hogy az eval paranccsal lehet ezt kijavítani, de nekem nem működik 🙁

megjegyzések

  • Igen, ez addig működik, amíg ‘ nincs szükség pl. fájlnevek beágyazott szóközökkel (vagy globális karaktereket tartalmazóakkal).

Válasz

Ha ez nem működik a "", akkor használja a ``:

abc=`ls -l /tmp/test/my\ dir/` 

Frissítés jobb mód:

abc=$(ls -l /tmp/test/my\ dir/) 

Megjegyzések

  • Ez a parancs eredményét egy változóban tárolja. Az OP (furcsa módon) magát a parancsot akarja egy változóba menteni. Ja, és valóban el kell kezdened használni a $( command... ) -t a háttérbillentyűk helyett.
  • Nagyon köszönöm a pontosításokat és tippeket!

Válasz

Mit szólnál egy python3 egysoroshoz?

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 .. 

Megjegyzések

  • A kérdés a BASH-ról szól, nem a pythonról.

Válasz

Egy másik egyszerű trükk a abc változó:

$ history -s $abc 

és nyomja meg a UpArrow vagy Ctrl-p parancsot. Minden más módszerrel ellentétben, így szükség esetén a végrehajtás előtt szerkesztheti.

Ez a parancs a változó tartalmat új bejegyzésként fűzi hozzá a Bash előzményekhez, és a UpArrow.

Vélemény, hozzászólás?

Az email címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük