Kann ein Shell-Skript sein Argument so drucken, wie Sie es an der Shell-Eingabeaufforderung schreiben würden?

9

Ich verstehe, dass in einem Shell-Skript "$@"die Skriptargumente erweitert werden und bei Bedarf zitiert werden. Dies leitet beispielsweise die Skriptargumente an gcc weiter:

gcc -fPIC "$@"

Bei Verwendung der bash Pass-to-stdin Syntax <<<aber "@$"nicht funktioniert , wie ich es erwarten würde.

#!/bin/bash
cat <<< "$@"

Das Skript wird als ./test.sh foo "bar baz"gibt

foo bar baz

Ich würde erwarten

foo "bar baz"

Gibt es eine Möglichkeit, ein Shell-Skript zu schreiben, das seine Argumente so druckt, wie Sie sie an der Shell-Eingabeaufforderung schreiben würden? Zum Beispiel: ein Hinweis darauf, welcher Befehl als nächstes verwendet werden soll, einschließlich der Skriptargumente im Hinweis.

Alex Jasmin
quelle

Antworten:

5

Nun, "$@"erweitert die Liste der Positionsparameter um ein Argument pro Positionsparameter.

Wenn Sie das tun:

set '' 'foo bar' $'blah\nblah'
cmd "$@"

cmdwird mit diesen 3 Argumenten aufgerufen: der leeren Zeichenfolge foo barund blah<newline>blah. Die Shell ruft den execve()Systemaufruf mit folgenden Elementen auf:

execve("/path/to/cmd", ["cmd", "", "foo bar", "blah\nblah"], [envvars...]);

Wenn Sie eine Shell-Befehlszeile (dh Code in der Shell-Sprache) rekonstruieren möchten, die denselben Aufruf reproduziert, können Sie Folgendes tun:

awk -v q="'" '
  function shellquote(s) {
    gsub(q, q "\\" q q, s)
    return q s q
  }
  BEGIN {
    for (i = 1; i < ARGC; i++) {
      printf "%s", sep shellquote(ARGV[i])
      sep = " "
    }
    printf "\n"
  }' cmd "$@"

Oder zshfragen Sie nach verschiedenen Arten von Zitaten:

set '' 'foo bar' $'blah\nblah'
$ print -r -- cmd "${(q)@}"
cmd '' foo\ bar blah$'\n'blah
$ print -r -- cmd "${(qq)@}"
cmd '' 'foo bar' 'blah
blah'
$ print -r -- cmd "${(qqq)@}"
cmd "" "foo bar" "blah
blah"
$ print -r -- cmd "${(qqqq)@}"
cmd $'' $'foo bar' $'blah\nblah'

Oder mit zsh, bashoder ksh93(hier bash, YMMV mit anderen Shells):

$ set '' 'foo bar' $'blah\nblah'
$ printf cmd; printf ' %q' "$@"; printf '\n'
cmd '' foo\ bar $'blah\nblah'

Sie können auch die Option xtrace der Shell verwenden, mit der die Shell druckt, was ausgeführt werden soll:

(PS4=; set -x; : cmd "$@")
: cmd '' 'foo bar' 'blah
blah'

Oben haben wir den :Befehl no-op mit cmdund die Positionsparameter als Argument ausgeführt. Meine Muschel druckte sie in einer gut zitierten Weise, die für die erneute Eingabe in die Muschel geeignet war. Das machen nicht alle Muscheln.

Stéphane Chazelas
quelle
5
`"$@"` expands to the script arguments, quoting them as needed

Nein, das passiert nicht. Das Aufrufen eines Programms erfordert eine Liste von Argumenten, wobei jedes Argument eine Zeichenfolge ist. Wenn Sie das Shell - Programm laufen ./test.sh foo "bar baz", baut diese ein Gespräch mit drei Argumenten: ./test.sh, foo, und bar baz. (Das nullte Argument ist der Programmname. Dadurch können Programme wissen, unter welchem ​​Namen sie aufgerufen werden.) Das Zitieren ist ein Merkmal der Shell, kein Merkmal von Programmaufrufen. Die Shell erstellt diese Liste beim Aufruf.

"$@"Kopiert die Liste der an das Skript oder die Funktion übergebenen Argumente direkt in die Liste der Argumente in dem Aufruf, in dem sie verwendet werden. Es ist kein Anführungszeichen erforderlich, da für diese Listen keine Shell-Analyse durchgeführt wird.

In verwenden cat <<< "$@"Sie "$@"in einem Kontext, in dem eine einzelne Zeichenfolge erforderlich ist. Der <<<Operator benötigt eine Zeichenfolge, keine Liste von Zeichenfolgen. In diesem Zusammenhang nimmt bash die Elemente der Liste und verbindet sie mit einem Leerzeichen dazwischen.

Wenn Sie beim Debuggen von Skripten ausführen set -x( set +xzum Deaktivieren), wird ein Ablaufverfolgungsmodus aktiviert, in dem jeder Befehl gedruckt wird, bevor er ausgeführt wird. In Bash enthält dieser Trace Anführungszeichen, die es ermöglichen, den Befehl wieder in eine Shell einzufügen (dies gilt nicht für jede shImplementierung).

Wenn Sie eine Zeichenfolge haben und diese in eine Shell-Quellensyntax umwandeln möchten, die zurück in die ursprüngliche Zeichenfolge analysiert wird, können Sie sie mit einfachen Anführungszeichen umgeben und jedes einzelne Anführungszeichen in der Zeichenfolge durch ersetzen '\''.

for x do
  printf %s "'${x//\'/\'\\\'\'}' "
done
echo

Die Syntax zum Ersetzen von Zeichenfolgen lautet ksh93 / bash / zsh / mksh-spezifisch. In normalem sh müssen Sie die Zeichenfolge durchlaufen.

for raw do
  quoted=
  while case "$raw" in *\'*) true;; *) false;; esac; do
    quoted="$quoted'\\''${raw%%\'*}"
    raw="${raw#*\'}"
  done
  printf %s "'$quoted$raw' "
done
echo
Gilles 'SO - hör auf böse zu sein'
quelle
2

"$@" Erweitert die Skriptargumente und zitiert sie nach Bedarf

Naja, so ungefähr. Aus praktischen Gründen sollte dies nah genug sein, und das Referenzhandbuch sagt dies aus"$@" is equivalent to "$1" "$2" ...

Mit den beiden Parametern foound bar bazwären diese also gleich:

echo "$@"
echo "$1" "$2"
echo "foo" "bar baz"

(Außer wenn die Parameter Sonderzeichen anstelle von einfachen Zeichenfolgen enthalten würden, würden sie nach dem Erweitern nicht erneut erweitert $@und $1...)

Aber selbst wenn wir in Betracht ziehen, $@durch die Parameter in Anführungszeichen ersetzt zu werden, wären die Anführungszeichen nicht zu echosehen, ähnlich wie gccdie Anführungszeichen auch nicht.

<<<ist eine kleine Ausnahme von der "$@"== "$1" "$2" ...Regel, es wird ausdrücklich erwähnt, dass The result is supplied as a single string to the command on its standard inputnach dem Durchlaufen der Parameter- und Variablenerweiterung und dem Entfernen von Anführungszeichen unter anderem. Also <<< "foo"gibt foowie immer nur als Eingabe, auf die gleiche Weise somecmd "foo"nur fooals Argument.

Das Skript als ./test.sh foo "bar baz"[...] zu bezeichnen, würde ich erwarten foo "bar baz"

Wenn die Zitate bleiben würden, müsste es immer noch sein "foo" "bar baz". Die Shell oder ein laufender Befehl hat keine Ahnung, wie das Zitat war, als der Befehl ausgeführt wurde. Oder wenn es überhaupt ein Anführungszeichen gab, erhält der Systemaufruf nur eine Liste von nullterminierten Zeichenfolgen, und Anführungszeichen sind nur ein Merkmal der Shell-Sprache. Andere Sprachen können andere Konventionen haben.

ilkkachu
quelle
0

Eine alternative Lösung für Bash

q='"'; t=( "${@/#/$q}" ); u=( "${t[@]/%/$q}" ); echo ${u[@]}

Bash unterstützt keine verschachtelten Ersetzungen. Vielen Dank an /programming/12303974/assign-array-to-variable#12304017, um zu zeigen, wie ein Array neu zugewiesen wird. Weitere Informationen zu Arrays, Erweiterung und Mustersubstitution (unter Parametererweiterung) finden Sie unter man bash( https://linux.die.net/man/1/bash ).

Analyse

Bash fügt die Befehlszeilenparameter als Array ein $@

q enthält das Anführungszeichen.

Doppelte Anführungszeichen um die Parametererweiterung behalten ${ ... }die einzelnen Parameter als unterschiedliche Elemente bei, und ( )wenn Sie sie einschließen, können Sie sie einer Variablen als Array zuweisen.

/#/$qIn einer Parametererweiterung wird der Anfang des Musters (wie Regex ^) durch das Anführungszeichen ersetzt.

/%/$qIn einer Parametererweiterung wird das Ende des Musters (wie bei Regex $) durch das Anführungszeichen ersetzt.

Anwendungsfall: Abfragen von MySQL nach einer Liste von E-Mail-Adressen über die Befehlszeile

Es gibt einige Änderungen an den obigen Anweisungen, um ein anderes Anführungszeichen zu verwenden, Kommas zwischen den Parametern einzufügen und das letzte Komma zu entfernen. Und natürlich bin ich schlecht, wenn ich das Passwort in den MySQL-Aufruf eingebe. So verklage mich.

q="'"; t=( "${@/#/$q}" ); u="${t[@]/%/$q,}"; v="u.email in( ${u%,} )"
mysql -uprod_program -h10.90.2.11 -pxxxxxxxxxxxx my_database <<END
select ...
from users u
join ....
where $v # <<<<<<<<<<<<<<<<<< here is where all the hard work pays off :-)
group by user_id, prog_id
;
END
Jeff
quelle
Beachten Sie nur, dass das Zitieren mit doppelten Anführungszeichen nicht sehr hilfreich ist, um Backslashes, $Erweiterungen und andere doppelte Anführungszeichen zu schützen . Aus diesem Grund verwenden die anderen Antworten einfache Anführungszeichen, während sie einige Längen verwenden, um einfache Anführungszeichen innerhalb der Zeichenfolge zu verarbeiten, oder verwenden die Shell-eigenen Funktionen, um eine Kopie der Zeichenfolge in Anführungszeichen zu erstellen.
Ilkkachu
@ilkkachu Ordnungsgemäß notiert! Deshalb habe ich (unter anderem) alle vorherigen Antworten positiv bewertet. Auch warum ich diesen Anwendungsfall hinzugefügt habe. Hoffentlich ist das Perfekte nicht der Feind des Guten.
Jeff