Come passare argomenti di script bash a una subshell


13

Ho uno script wrapper che fa un po 'di lavoro e quindi passa i parametri originali su un altro strumento:

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

Funziona bene, a meno che "l'altro strumento" non sia eseguito in una sottoshell:

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

Se chiamo il mio script wrapper in questo modo:

wrapper.sh -x "blah blup"

quindi, solo il primo argomento originale (-x) viene passato a "altro_tool". In realtà, non creo una subshell, ma trasmetto gli argomenti originali a una shell su un telefono Android, che non dovrebbe fare alcuna differenza:

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

Risposte:


14

Il printfcomando di Bash ha una funzione che citerà / escape / qualunque sia una stringa, quindi fintanto che sia il genitore che la subshell sono effettivamente bash, questo dovrebbe funzionare:

[Modifica: come ha sottolineato Siegi in un commento, se lo fai nel modo ovvio c'è un problema quando non vengono forniti argomenti, dove si comporta come se in realtà ci fosse un solo argomento vuoto. Ho aggiunto una soluzione alternativa di seguito, avvolgendo la stringa di formato con ${1+}, che include la stringa di formato solo se viene definito il primo argomento . È un po 'un kluge, ma funziona.]

#!/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"

Nota che puoi anche farlo in una sola riga: bash -c "other_tool -a -b$(printf "${1+ %q}" "$@")"


Grazie! Questo ha funzionato molto bene per me.
— DribblzAroundU82

Questo non funziona correttamente quando non viene passato alcun argomento (passa un singolo argomento vuoto anziché nessun argomento).
— siegi,

1
@siegi Buona cattura; Ho aggiunto una soluzione alternativa per questo.
— Gordon Davisson,

4

Nessuna delle soluzioni funziona bene. Basta passare x / \ \ \ "b \" / aaaaa / \ 'xxx \ yyyy \' / zz \ ​​"offf \" come parametro e falliscono.

Ecco un semplice wrapper che gestisce ogni caso. Nota come evade due volte ogni argomento.

#!/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[*]}

1
utile anche se stai cercando di concatenare $ @ come parte di un eventuale argomento stringa citato come '/ bin / foo' "$ @" '--opt' e scoprire che semplicemente non esce come ti aspetteresti .
— jrg

1
Oh grazie a Dio. Sono stato alle prese con questo problema e apparentemente nulla ha funzionato. Questo ha risolto. Sarebbe bello se fosse in qualche modo un bash incorporato per invocare doppie fughe considerando che questo problema si presenta molto.
— MooGoo,

2

Cambia $@in $*. Ho fatto un piccolo test locale e funziona nel mio caso.

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

Salvare come test.she renderlo eseguibile dà

$ ./test.sh foo bar
foo bar
foo

C'è una sottile differenza tra $*e $@, come puoi vedere. Vedi ad esempio http://ss64.com/bash/syntax-parameters.html


Per la domanda di follow-up nei commenti: è necessario sfuggire, ad esempio, allo spazio bianco "due volte" per passare una stringa con un separatore come argomento combinato, ad esempio con test.shmodificato in un wcwrapper:

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

Questo funziona:

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

ma:

$ ./test.sh -l "test file"
wc: test: No such file or directory
wc: file: No such file or directory
0 total

Sfortunatamente, neanche questo funziona. Se si chiama il wrapper in questo modo: wrapper.sh -x "blah blup"allora la subshell ottiene TRE parametri (-x, blah, blup) invece di DUE (-x, "blah blup")
— Ralf Holly

È necessario "doppio escape" dell'argomento se si desidera preservare il separatore di spazi, ad es "Blah\ blup". Provalo e vedi se funziona.
— Daniel Andersson,

2
Sembra che funzioni, tuttavia ciò richiederebbe al chiamante di wrapper.sh di aggiungere escape e sto cercando una soluzione trasparente.
— Ralf Holly,

2

Non riesce perché stai forzando una matrice (i parametri posizionali) in una stringa. "$@"è magico perché ti dà ogni parametro separato come una stringa opportunamente quotata. L'aggiunta di testo aggiuntivo spezza la magia: "blah $@"è solo una singola stringa.

Questo potrebbe farti avvicinare:

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

Naturalmente, qualsiasi parametro che contiene una singola virgoletta causerà problemi.


2

Ok, altre spiegazioni:

$ 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

1

Ho provato molte soluzioni diverse, una buona risorsa che include informazioni di base e alternative è ad esempio BashFAQ / 096 sul Wiki di Greg (aka. GreyCat) . Complessivamente ho trovato i due seguenti i più leggibili (da quelli funzionanti):


Da Bash 4.4 (per quanto ne so dalle NEWS ) è possibile utilizzare l' espansione dei parametri in @Q questo modo:

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

Nota che io uso $*qui invece che $@perché vuoi "other_tool -a -b ${*@Q}"essere una stringa singola anziché una stringa per argomento passato.

Se vuoi fare lo stesso con una variabile array bash, avrai bisogno della sintassi ${ARRAY[*]@Q}(tra virgolette).


Se non hai Bash 4.4 o versione successiva o non sei sicuro, questa è la mia soluzione preferita:

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

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

Si noti che qui è necessario utilizzare "$@"anziché $@o "$*"o $*perché non si desidera la suddivisione delle parole all'interno degli argomenti, quindi le varianti senza virgolette non possono essere utilizzate e si desidera conservare il numero di argomenti, quindi "$*"non può essere utilizzato come si unirebbe tutti gli argomenti in una singola stringa. La funzione restituisce quindi tutti gli argomenti in una singola stringa.

Se non ti interessa lo spazio aggiuntivo davanti al primo argomento, puoi modificare la printfstringa di formato in " %q"e rimuovere la separatorvariabile. Oppure puoi usare il one-liner della risposta di Gordon Davissons .


Queste soluzioni funzionano con tutti i casi che ho potuto trovare, in particolare:

  • Nessun argomento: escapeBashArgs→ niente
  • Argomenti vuoti: escapeBashArgs "" ""→'' ''
  • Argomenti con solo spazi: escapeBashArgs " " " "→ ' ' ' 'o \ \ \ \ \( l'ultimo spazio viene mangiato da questo renderer di siti )
  • Argomenti con spaziatura speciale e newline : escapeBashArgs "a b" c\ d "arg with newline"→ 'a b' 'c d' $'arg with\nnewline'o a\ \ \ \ \ \ b c\ d $'arg with\nnewline'( la newline è tra withe newline, in altre posizioni è a causa del ritorno a capo su questo sito )
  • Argomenti con caratteri speciali: escapeBashArgs '$"'\''({:})'→ '$"'\''({:})'o\$\"\'\(\{:\}\)
  • Esempio dalla risposta di jcayzacs : escapeBashArgs x/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"→ 'x/ "b"/aaaaa/'\''xxx yyyy'\''/zz"offf"'ox/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"

(Testato con GNU bash 5.0.3 (1)-release).


-2
#! /bin/bash
sh -c 'other_tool -a -b "$@"' -- "$@"

3
Benvenuto in Super User! Siamo alla ricerca di risposte lunghe che forniscano una spiegazione e un contesto. Non dare solo una risposta di una riga; spiega perché la tua risposta è giusta, idealmente con citazioni. Le risposte che non includono spiegazioni potrebbero essere rimosse.
— G-Man dice "Reinstate Monica"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.