Jak przekazać argumenty skryptu bash do podpowłoki

13

Mam skrypt otoki, który działa trochę, a następnie przekazuje oryginalne parametry do innego narzędzia:

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

Działa to dobrze, chyba że „inne narzędzie” działa w podpowłoce:

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

Jeśli wywołam mój skrypt otoki w następujący sposób:

wrapper.sh -x "blah blup"

wtedy tylko pierwszy oryginalny argument (-x) jest przekazywany do „other_tool”. W rzeczywistości nie tworzę podpowłoki, ale przekazuję oryginalne argumenty powłoce na telefonie z Androidem, co nie powinno mieć znaczenia:

#!/bin/bash
# ...
adb sh -c "other_tool -a -b $@"
Ralf Holly
źródło

Odpowiedzi:

14

printfPolecenie Basha ma funkcję, która będzie cytować / uciec / jakikolwiek ciąg, więc tak długo, jak zarówno rodzic, jak i podpowłoka są bashami, powinno to działać:

[Edycja: jak zauważył siegi w komentarzu, jeśli zrobisz to w oczywisty sposób, pojawia się problem, gdy nie podano żadnych argumentów, a zachowuje się tak, jakby faktycznie był jeden pusty argument. Poniżej dodałem obejście, zawijające ciąg formatu ${1+}, który obejmuje ciąg formatu tylko wtedy, gdy zdefiniowany jest pierwszy argument . To trochę dziwne, ale działa.]

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

Pamiętaj, że możesz to również zrobić w jednym wierszu: bash -c "other_tool -a -b$(printf "${1+ %q}" "$@")"

Gordon Davisson
źródło
Dziękuję Ci! To działało dla mnie bardzo dobrze.
DribblzAroundU82
To nie działa poprawnie, gdy nie jest przekazywany żaden argument (przekazuje pojedynczy pusty argument zamiast żadnego argumentu).
siegi
1
@siegi Good catch; Dodałem do tego obejście.
Gordon Davisson
4

Żadne z rozwiązań nie działa dobrze. Wystarczy podać x / \ \ \ "b \" / aaaaa / \ 'xxx \ rrrr \' / zz \ ​​"offf \" jako parametr i nie powiodą się.

Oto proste opakowanie, które obsługuje każdą skrzynkę. Zwróć uwagę, jak dwukrotnie unika każdego argumentu.

#!/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
źródło
1
przydatne również, jeśli próbujesz połączyć $ @ jako część ewentualnego argumentu ciąg znaków cytowanego, takiego jak „/ bin / foo” „$ @” --opt ”i odkrywasz, że nie wychodzi on tak, jak można się spodziewać .
jrg
1
Dzięki bogu. Walczyłem z tym problemem i pozornie nic nie działało. To rozwiązało. Byłoby miło, gdyby był to jakiś wbudowany bash do wywoływania podwójnych ucieczek, biorąc pod uwagę, że ten problem często się pojawia.
MooGoo,
2

Zmień $@na $*. Zrobiłem mały test lokalny i działa w moim przypadku.

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

Zapisywanie jako test.shi wykonywanie go daje

$ ./test.sh foo bar
foo bar
foo

Jak widać, istnieje subtelna różnica między $*i $@. Zobacz np. Http://ss64.com/bash/syntax-parameters.html


Aby odpowiedzieć na pytanie uzupełniające w komentarzach: musisz uciec np. „Dwa razy” spacją, aby przekazać ciąg z separatorem jako połączony argument, np. Ze test.shzmodyfikowanym wcopakowaniem:

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

To działa:

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

ale:

$ ./test.sh -l "test file"
wc: test: No such file or directory
wc: file: No such file or directory
0 total
Daniel Andersson
źródło
Niestety to też nie działa. Jeśli wywołasz otoki w ten sposób: wrapper.sh -x "blah blup"wówczas podpowłoka otrzymuje TRZY parametry (-x, bla, blup) zamiast DWA (-x, „bla blup”)
Ralf Holly
Musisz „podwójnie uciec” argumentu, jeśli chcesz zachować separator spacji, tj "Blah\ blup". Wypróbuj i sprawdź, czy to działa.
Daniel Andersson
2
Wygląda na to, że działa, ale wymagałoby to od osoby wywołującej wrapper.sh dodania znaków ucieczki, a ja szukam przejrzystego rozwiązania.
Ralf Holly
2

Nie udaje się, ponieważ zmuszasz tablicę (parametry pozycyjne) do łańcucha. "$@"jest magiczny, ponieważ daje ci każdy osobny parametr jako poprawnie cytowany ciąg. Dodanie dodatkowego tekstu przerywa magię: "blah $@"jest tylko jednym ciągiem.

Może cię to przybliżyć:

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

Oczywiście każdy parametr zawierający pojedynczy cytat spowoduje problemy.

Glenn Jackman
źródło
2

Ok, więcej objaśnień:

$ 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
Nocnik
źródło
1

Wypróbowałem wiele różnych rozwiązań, dobrym źródłem informacji, w tym podstawowymi informacjami i alternatywami, jest na przykład BashFAQ / 096 na Wiki Grega (alias. GreyCat) . W sumie dwa następujące są najbardziej czytelne (z działających):


Od wersji Bash 4.4 (o ile mogłem powiedzieć z AKTUALNOŚCI ) możliwe jest użycie rozszerzenia parametrów w @Q następujący sposób:

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

Zauważ, że używam $*tutaj zamiast, $@ponieważ chcesz "other_tool -a -b ${*@Q}"być jednym ciągiem zamiast jednego ciągu na przekazany argument.

Jeśli chcesz zrobić to samo ze zmienną tablicy bash , potrzebujesz składni ${ARRAY[*]@Q}(w cudzysłowach).


Jeśli nie masz dostępnej wersji Bash 4.4 lub nowszej lub nie masz pewności, oto moje preferowane rozwiązanie:

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

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

Zauważ, że tutaj musisz użyć "$@"zamiast $@lub "$*"lub $*ponieważ nie chcesz dzielenia słów w argumentach, więc nie można użyć wariantów bez cudzysłowu i chcesz zachować liczbę argumentów, więc "$*"nie można użyć, jak by się przyłączyła wszystkie argumenty w jednym ciągu. Następnie funkcja zwraca wszystkie argumenty w jednym ciągu.

Jeśli nie zależy ci na dodatkowej spacji przed pierwszym argumentem, możesz zmienić printfciąg formatu na " %q"i usunąć separatorzmienną. Lub możesz użyć jednowierszowej odpowiedzi Gordona Davissona .


Te rozwiązania działają ze wszystkimi przypadkami, które mógłbym wymyślić, w szczególności:

  • Brak argumentów: escapeBashArgsnic
  • Puste argumenty: escapeBashArgs "" ""'' ''
  • Argumenty zawierające tylko spacje: escapeBashArgs " " " "' ' ' 'lub \ \ \ \ \( ostatnia przestrzeń jest zjedzona przez ten renderer stron )
  • Argumenty ze specjalnymi odstępami i nowymi liniami: escapeBashArgs "a b" c\ d "arg with newline"'a b' 'c d' $'arg with\nnewline'lub a\ \ \ \ \ \ b c\ d $'arg with\nnewline'( nowa linia znajduje się pomiędzy withi newline, w innych pozycjach wynika z zawijania linii na tej stronie )
  • Argumenty ze znakami specjalnymi: escapeBashArgs '$"'\''({:})''$"'\''({:})'lub\$\"\'\(\{:\}\)
  • Przykład z odpowiedzi jcayzacs : escapeBashArgs x/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"'x/ "b"/aaaaa/'\''xxx yyyy'\''/zz"offf"'lubx/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"

(Testowane z wydaniem GNU bash 5.0.3 (1).)

siegi
źródło
-2
#! /bin/bash
sh -c 'other_tool -a -b "$@"' -- "$@"
Nocnik
źródło
3
Witamy w Super User! Szukamy długich odpowiedzi, które zawierają wyjaśnienia i kontekst. Nie udzielaj odpowiedzi tylko w jednym wierszu; wyjaśnij, dlaczego Twoja odpowiedź jest poprawna, najlepiej z cytatami. Odpowiedzi, które nie zawierają wyjaśnień, mogą zostać usunięte.
G-Man mówi „Reinstate Monica”