Czy skrypt powłoki może wydrukować swój argument cytowany tak, jak napisałbyś go w wierszu poleceń powłoki?

9

W skrypcie powłoki rozumiem, że "$@"rozszerzam się na argumenty skryptu, cytując je w razie potrzeby. Na przykład przekazuje argumenty skryptu do gcc:

gcc -fPIC "$@"

<<<Jednak podczas korzystania ze składni bash pass-to-stdin "@$"nie działa tak, jak bym tego oczekiwał.

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

Wywołanie skryptu jako ./test.sh foo "bar baz"daje

foo bar baz

oczekiwałbym

foo "bar baz"

Czy istnieje sposób na napisanie skryptu powłoki, który wypisze jego argumenty, tak jak napisałbyś je w wierszu poleceń powłoki? Na przykład: podpowiedź, której komendy użyć następnie, w tym argumenty skryptu w podpowiedzi.

Alex Jasmin
źródło

Odpowiedzi:

5

Cóż, "$@"rozwija się do listy parametrów pozycyjnych, jeden argument na parametr pozycyjny.

Kiedy to zrobisz:

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

cmdjest wywoływany z tymi 3 argumentami: pusty ciąg foo bari blah<newline>blah. Powłoka wywoła wywołanie execve()systemowe za pomocą czegoś takiego:

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

Jeśli chcesz zrekonstruować wiersz poleceń powłoki (czyli kod w języku powłoki), który odtworzyłby to samo wywołanie, możesz zrobić coś takiego:

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 "$@"

Lub z zshpytaniem o różne typy cytatów:

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'

Lub z zsh, bashlub ksh93(tutaj dla bashYMMV z innymi powłokami):

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

Możesz także użyć opcji xtrace powłoki, która powoduje, że powłoka drukuje to, co zamierza wykonać:

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

Powyżej uruchomiliśmy polecenie :no-op z cmdparametrami pozycyjnymi jako argumentem. Moja skorupa wydrukowała je w ładny cytat, nadający się do ponownego włożenia do powłoki. Nie wszystkie muszle to robią.

Stéphane Chazelas
źródło
5
`"$@"` expands to the script arguments, quoting them as needed

Nie, nie tak się dzieje. Wywołanie programu wymaga listy argumentów, przy czym każdy argument jest łańcuchem. Po uruchomieniu program powłoki ./test.sh foo "bar baz", to tworzy połączenia z trzema argumentami: ./test.sh, foo, i bar baz. (Argument zerowy to nazwa programu; pozwala to programom wiedzieć, pod jaką nazwą są wywoływane.) Cytowanie jest cechą powłoki, a nie funkcją wywołań programu. Powłoka buduje tę listę podczas wykonywania połączenia.

"$@"bezpośrednio kopiuje listę argumentów przekazywanych do skryptu lub funkcji na listę argumentów w wywołaniu, w którym jest używana. Nie ma potrzeby cytowania, ponieważ na tych listach nie przeprowadzono analizy powłoki.

W cat <<< "$@"używasz "$@"w kontekście, w którym wymagany jest pojedynczy ciąg. <<<Podmiot gospodarczy wymaga ciąg, a nie listę ciągów. W tym kontekście bash pobiera elementy listy i łączy je ze spacją między nimi.

W przypadku debugowania skryptu, jeśli uruchomisz set -x( set +xaby wyłączyć), aktywuje tryb śledzenia, w którym każde polecenie jest drukowane przed wykonaniem. W bash ten ślad zawiera cudzysłowy, które umożliwiają wklejenie polecenia z powrotem do powłoki (nie dotyczy to każdej shimplementacji).

Jeśli masz ciąg znaków i chcesz przekształcić go w składnię źródłową powłoki, która analizuje z powrotem w oryginalny ciąg, możesz go otoczyć pojedynczymi cudzysłowami i zastąpić każdy pojedynczy cudzysłów wewnątrz łańcucha '\''.

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

Składnia zamiany łańcucha jest specyficzna dla ksh93 / bash / zsh / mksh. W zwykłym sh musisz zapętlić łańcuch.

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- przestań być zły”
źródło
2

"$@" rozwija się do argumentów skryptu, cytując je w razie potrzeby

Cóż, w pewnym sensie. Ze względów praktycznych powinno to być wystarczająco blisko, a instrukcja obsługi tak mówi"$@" is equivalent to "$1" "$2" ...

Tak więc, z dwoma parametrami fooi bar bazbyłyby one podobne:

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

(Z wyjątkiem tego, że jeśli parametry zawierają znaki specjalne zamiast zwykłych ciągów, nie będą ponownie rozwijane po rozwinięciu $@i $1...)

Ale nawet jeśli rozważymy $@zastąpienie go parametrami w cytatach, cytaty nie byłyby tam echodo zobaczenia, podobnie jak to, że gccnie dostają również cytatów.

<<<jest trochę wyjątkiem od reguły "$@"== "$1" "$2" ..., wyraźnie wspomniano, że The result is supplied as a single string to the command on its standard inputpo przejściu między rozszerzaniem parametrów i zmiennych oraz usuwaniem cytatów. Jak zwykle <<< "foo"podaje tylko foojako dane wejściowe, w ten sam sposób somecmd "foo"podaje tylko foojako argument.

Nazywałbym skrypt jako ./test.sh foo "bar baz"[...] oczekiwałbym foo "bar baz"

Gdyby cytaty pozostały, nadal musiałoby tak być "foo" "bar baz". Powłoka lub jakiekolwiek uruchomione polecenie nie ma pojęcia, jakie było cytowanie, gdy polecenie zostało uruchomione. Lub jeśli byłby nawet cytat, o którym mowa, wywołanie systemowe po prostu otrzymuje listę łańcuchów zakończonych znakiem zerowym, a cytaty są tylko cechą języka powłoki. Inne języki mogą mieć inne konwencje.

ilkkachu
źródło
0

Alternatywne rozwiązanie dla bash

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

Bash nie obsługuje zagnieżdżonych podstawień, więc dzięki /programming/12303974/assign-array-to-variable#12304017 za pokazanie, jak zmienić przypisanie tablicy. Zobacz man bash( https://linux.die.net/man/1/bash ), aby uzyskać szczegółowe informacje na temat tablic, interpretacji i podstawiania wzorców (w części ekspansja parametrów).

Analiza

Bash umieszcza parametry wiersza poleceń jako tablicę $@

q zawiera znak cytowania.

Podwójne cudzysłowy wokół rozwijania parametrów ${ ... }zachowują poszczególne parametry jako odrębne elementy, a zawijanie ich ( )pozwala przypisać je jako zmienne do tablicy.

/#/$qw rozwinięciu parametru podstawia początek wzorca (podobnie jak regex ^) znakiem cudzysłowu.

/%/$qw rozwinięciu parametru zastępuje koniec wzorca (jak regex $) znakiem cudzysłowu.

Przypadek użycia: zapytanie MySQL o listę adresów e-mail z wiersza poleceń

W powyższych instrukcjach wprowadzono kilka zmian, aby użyć innego znaku cytowania, dodać przecinki między parametrami i usunąć przecinek końcowy. I oczywiście jestem zły, umieszczając hasło w wywołaniu mysql. Więc pozwij mnie.

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
źródło
Pamiętaj, że cytowanie z podwójnymi cudzysłowami nie jest bardzo pomocne w ochronie odwrotnych ukośników, $rozwinięć i innych podwójnych cudzysłowów. Dlatego w innych odpowiedziach używane są pojedyncze cudzysłowy, a niektóre długości służą do obsługi pojedynczych cudzysłowów w ciągu lub wykorzystują własne funkcje powłoki do tworzenia cytowanej kopii łańcucha.
ilkkachu
@ilkkachu Należy zauważyć! Dlatego (między innymi) głosowałem za wszystkimi poprzednimi odpowiedziami. Również dlaczego dodałem ten przypadek użycia. Mamy nadzieję, że ideał nie jest wrogiem dobra.
Jeff