So übergeben Sie Bash-Skriptargumente an eine Subshell

13

Ich habe ein Wrapper-Skript, das einige Arbeiten erledigt und dann die ursprünglichen Parameter an ein anderes Tool weitergibt:

#!/bin/bash
# ...
other_tool -a -b "$@"

Dies funktioniert einwandfrei, es sei denn, das "andere Tool" wird in einer Subshell ausgeführt:

#!/bin/bash
# ...
bash -c "other_tool -a -b $@"

Wenn ich mein Wrapper-Skript so aufrufe:

wrapper.sh -x "blah blup"

dann wird nur das erste ursprüngliche Argument (-x) an "other_tool" übergeben. In Wirklichkeit erstelle ich keine Subshell, sondern übergebe die ursprünglichen Argumente an eine Shell auf einem Android-Handy, was keinen Unterschied machen sollte:

#!/bin/bash
# ...
adb sh -c "other_tool -a -b $@"
Ralf Holly
quelle

Antworten:

14

Der printfBefehl von Bash verfügt über eine Funktion, die Anführungszeichen / Escapezeichen / eine beliebige Zeichenfolge angibt. Solange also sowohl die übergeordnete als auch die Unterschale tatsächlich Bash sind, sollte dies funktionieren:

[Bearbeiten: Wie Siegi in einem Kommentar betonte, gibt es ein Problem, wenn keine Argumente angegeben werden, wenn es so aussieht, als gäbe es tatsächlich ein einziges leeres Argument. Ich habe unten eine Problemumgehung hinzugefügt, mit der die Formatzeichenfolge umbrochen wird, die die Formatzeichenfolge ${1+}nur enthält, wenn das erste Argument definiert ist . Es ist ein bisschen kluge, aber es funktioniert.]

#!/bin/bash

quoted_args="$(printf "${1+ %q}" "$@")" # Note: this will have a leading space before the first arg
# echo "Quoted args:$quoted_args" # Uncomment this to see what it's doing
bash -c "other_tool -a -b$quoted_args"

Beachten Sie, dass Sie dies auch in einer einzelnen Zeile tun können: bash -c "other_tool -a -b$(printf "${1+ %q}" "$@")"

Gordon Davisson
quelle
Vielen Dank! Das hat bei mir sehr gut funktioniert.
DribblzAroundU82
Dies funktioniert nicht richtig, wenn kein Argument übergeben wird (es wird ein einzelnes leeres Argument anstelle eines Arguments übergeben).
Siegi
1
@siegi Guter Fang; Ich habe eine Problemumgehung hinzugefügt.
Gordon Davisson
4

Keine der Lösungen funktioniert gut. Übergeben Sie einfach x / \ "b" / aaaaa / "xxx \ yyyy" / zz "offf" als Parameter und sie schlagen fehl.

Hier ist ein einfacher Wrapper, der jeden Fall behandelt. Beachten Sie, wie jedes Argument zweimal umgangen wird.

#!/usr/bin/env bash
declare -a ARGS
COUNT=$#
for ((INDEX=0; INDEX<COUNT; ++INDEX))
do
    ARG="$(printf "%q" "$1")"
    ARGS[INDEX]="$(printf "%q" "$ARG")"
    shift
done

ls -l ${ARGS[*]}
jcayzac
quelle
1
Dies ist auch nützlich, wenn Sie versuchen, $ @ als Teil eines eventuell zitierten String-Arguments wie '/ bin / foo' "$ @" '--opt' zu verketten und feststellen, dass es einfach nicht wie erwartet herauskommt .
jrg
1
Oh Gott sei Dank. Ich hatte Probleme mit diesem Problem und anscheinend hat nichts funktioniert. Dies löste es. Wäre schön, wenn es irgendwie eine eingebaute Bash wäre, um doppelte Fluchten aufzurufen, wenn man bedenkt, dass dieses Problem häufig auftritt.
MooGoo
2

Wechseln Sie $@zu $*. Ich habe einen kleinen lokalen Test gemacht und es funktioniert in meinem Fall.

#!/bin/sh
bash -c "echo $*"
bash -c "echo $@"

Speichern als test.shund ausführbar machen gibt

$ ./test.sh foo bar
foo bar
foo

Es gibt einen subtilen Unterschied zwischen $*und $@, wie Sie sehen können. Siehe z. B. http://ss64.com/bash/syntax-parameters.html


Für die Folgefrage in den Kommentaren: Sie müssen zB Leerzeichen "zweimal" maskieren, um eine Zeichenfolge mit einem Trennzeichen als kombiniertes Argument zu übergeben, z. B. mit test.sheinem wcWrapper geändert :

#!/bin/sh
bash -c "wc $*"

Das funktioniert:

$ touch test\ file
$ ./test.sh -l "test\ file"
0 test file

aber:

$ ./test.sh -l "test file"
wc: test: No such file or directory
wc: file: No such file or directory
0 total
Daniel Andersson
quelle
Leider funktioniert das auch nicht. Wenn Sie den Wrapper so aufrufen: wrapper.sh -x "blah blup"Dann erhält die Subshell DREI Parameter (-x, bla, blup) anstelle von ZWEI (-x, "bla bla")
Ralf Holly
Sie müssen das Argument "doppelt maskieren", wenn das Leerzeichen getrennt werden soll, d "Blah\ blup". H. Probieren Sie es aus und sehen Sie, ob es funktioniert.
Daniel Andersson
2
Es sieht so aus, als würde es funktionieren. Dies würde jedoch erfordern, dass der Aufrufer von wrapper.sh Escapezeichen hinzufügt, und ich suche nach einer transparenten Lösung.
Ralf Holly
2

Es schlägt fehl, weil Sie ein Array (die Positionsparameter) in eine Zeichenfolge zwingen. "$@"ist magisch, weil es Ihnen jeden einzelnen Parameter als richtig zitierte Zeichenfolge gibt. Das Hinzufügen von zusätzlichem Text unterbricht die Magie: "blah $@"ist nur eine einzelne Zeichenfolge.

Dies kann Sie näher bringen:

cmd="other_tool -a -b"
for parm in "$@"; do cmd+=" '$parm'"; done
adb sh -c "$cmd"

Natürlich verursacht jeder Parameter, der ein einfaches Anführungszeichen enthält, Probleme.

Glenn Jackman
quelle
2

Ok, mehr Erklärungen:

$ cat /tmp/test.sh
#! /bin/bash

echo '$@='"$@"

set -v # "debug"

sh -c 'echo $@' "$@" # gremlins steal the first option

sh -c 'echo $@' -- "$@" # We defend the option with two knifes

$ bash -e /tmp/test.sh first second third
$@=first second third

sh -c 'echo $@' "$@" # gremlins steal the first option
second third

sh -c 'echo $@' -- "$@" # We defend the option with two knifes
first second third
Jerry
quelle
1

Ich habe viele verschiedene Lösungen ausprobiert. Eine gute Ressource mit Hintergrundinformationen und Alternativen ist beispielsweise BashFAQ / 096 in Gregs (auch bekannt als GreyCats) Wiki . Insgesamt fand ich die folgenden zwei am besten lesbar (von den funktionierenden):


Seit Bash 4.4 (soweit ich aus den NEWS ersehen kann ) ist es möglich, die Parametererweiterung @Q wie folgt zu verwenden :

adb sh -c "other_tool -a -b ${*@Q}"

Beachten Sie, dass ich $*hier anstelle von verwende, $@weil Sie "other_tool -a -b ${*@Q}"eine einzelne Zeichenfolge anstelle einer Zeichenfolge pro übergebenem Argument sein möchten .

Wenn Sie dasselbe mit einer Bash-Array-Variablen tun möchten, benötigen Sie die Syntax ${ARRAY[*]@Q}(innerhalb der Anführungszeichen).


Wenn Sie Bash 4.4 oder höher nicht verfügbar haben oder sich nicht sicher sind, ist dies meine bevorzugte Lösung:

function escapeBashArgs() {
    local arg separator=""
    for arg
    do
        printf "%s%q" "$separator" "$arg"
        separator=" "
    done
}

adb sh -c "other_tool -a -b $(escapeBashArgs "$@")"

Beachten Sie, dass Sie hier "$@"anstelle von $@oder "$*"oder verwenden müssen, $*weil Sie keine Wortaufteilung innerhalb von Argumenten wünschen, sodass die Varianten ohne Anführungszeichen nicht verwendet werden können und die Anzahl der Argumente beibehalten werden soll, sodass "$*"sie nicht so verwendet werden können, wie sie sich verbinden würden alle Argumente zu einer einzelnen Zeichenfolge. Die Funktion gibt dann alle Argumente in einer einzelnen Zeichenfolge zurück.

Wenn Sie nicht über den zusätzlichen Raum vor dem ersten Argument ist es egal, können Sie das ändern printfFormatstring " %q"und die entfernen separatorVariable. Oder Sie können den Einzeiler von Gordon Davissons Antwort verwenden .


Diese Lösungen funktionieren in allen Fällen, die mir einfallen könnten, insbesondere:

  • Keine Argumente: escapeBashArgsnichts
  • Leere Argumente: escapeBashArgs "" ""'' ''
  • Argumente mit nur Leerzeichen: escapeBashArgs " " " "' ' ' 'oder \ \ \ \ \( das letzte Leerzeichen wird von diesem Site-Renderer gegessen )
  • Argumente mit speziellen Abständen und Zeilenumbrüchen: escapeBashArgs "a b" c\ d "arg with newline"'a b' 'c d' $'arg with\nnewline'oder a\ \ \ \ \ \ b c\ d $'arg with\nnewline'( die Zeilenumbruchstelle befindet sich zwischen withund newlinean anderen Positionen liegt dies am Zeilenumbruch auf dieser Site )
  • Argumente mit Sonderzeichen: escapeBashArgs '$"'\''({:})''$"'\''({:})'oder\$\"\'\(\{:\}\)
  • Beispiel von jcayzacs Antwort : escapeBashArgs x/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"'x/ "b"/aaaaa/'\''xxx yyyy'\''/zz"offf"'oderx/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"

(Getestet mit GNU Bash 5.0.3 (1) -Release.)

siegi
quelle
-2
#! /bin/bash
sh -c 'other_tool -a -b "$@"' -- "$@"
Jerry
quelle
3
Willkommen bei Super User! Wir suchen nach langen Antworten, die Erklärungen und Zusammenhänge liefern. Geben Sie nicht nur eine einzeilige Antwort. Erklären Sie, warum Ihre Antwort richtig ist, idealerweise mit Zitaten. Antworten, die keine Erklärungen enthalten, werden möglicherweise entfernt.
G-Man sagt "Reinstate Monica"