Um script de shell pode imprimir seu argumento, citado como você os escreveria no prompt do shell?

9

Em um script de shell, meu entendimento é que se "$@"expande para os argumentos do script, citando-os conforme necessário. Por exemplo, isso encaminha os argumentos do script para o gcc:

gcc -fPIC "$@"

<<<Porém, ao usar a sintaxe bash pass-to-stdin , "@$"não funciona como eu esperava.

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

Chamar o script como ./test.sh foo "bar baz"

foo bar baz

eu esperaria

foo "bar baz"

Existe uma maneira de escrever um script de shell que imprima seus argumentos como você os escreveria no prompt do shell? Por exemplo: uma dica sobre qual comando usar a seguir, incluindo os argumentos do script na dica.

Alex Jasmin
fonte

Respostas:

5

Bem, se "$@"expande para a lista de parâmetros posicionais, um argumento por parâmetro posicional.

Quando você faz:

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

cmdestá sendo invocado com esses 3 argumentos: a string vazia foo bare blah<newline>blah. O shell chamará a chamada do execve()sistema com algo como:

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

Se você deseja reconstruir uma linha de comando do shell (que é código na linguagem do shell) que reproduz a mesma chamada, você pode fazer algo como:

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

Ou zsh, solicitando diferentes tipos de citações:

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'

Ou com zsh, bashou ksh93(aqui para bashYMMV com outras conchas):

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

Você também pode usar a opção xtrace do shell, que faz com que o shell imprima o que será executado:

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

Acima, executamos o :comando no-op com cmde os parâmetros posicionais como argumento. Meu shell os imprimiu de uma maneira agradável e citada, adequada para reintroduzir no shell. Nem todas as conchas fazem isso.

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

Não, não é isso que acontece. Chamar um programa leva uma lista de argumentos, cada argumento sendo uma sequência. Quando você executa o programa de shell ./test.sh foo "bar baz", este constrói uma chamada com três argumentos: ./test.sh, foo, e bar baz. (O argumento zeroth é o nome do programa; isso permite que os programas saibam sob que nome eles são chamados.) A citação é um recurso do shell, não um recurso de chamadas de programa. O shell cria essa lista quando faz a chamada.

"$@"copia diretamente a lista de argumentos passados ​​para o script ou função para a lista de argumentos na chamada em que é usada. Não há cotação envolvida, pois não há análise de shell feita nessas listas.

Em cat <<< "$@", você está usando "$@"em um contexto em que uma única string é necessária. O <<<operador` requer uma string, não uma lista de strings. Nesse contexto, o bash pega os elementos da lista e os une a um espaço intermediário.

Para depuração de script, se você executar set -x( set +xpara desativar), isso ativa um modo de rastreamento no qual cada comando é impresso antes da execução. No bash, esse rastreio possui aspas que possibilitam colar o comando novamente em um shell (isso não ocorre em todas as shimplementações).

Se você possui uma string e deseja transformá-la na sintaxe de origem do shell que analisa de volta na string original, pode cercá-la com aspas simples e substituir todas as aspas dentro da string por '\''.

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

A sintaxe de substituição de cadeia de caracteres é específica para ksh93 / bash / zsh / mksh. No sh simples, você precisa fazer um loop sobre a string.

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- parar de ser mau'
fonte
2

"$@" expande para os argumentos do script, citando-os conforme necessário

Bem, mais ou menos. Para propósitos práticos, deve ser suficientemente próximo, e o manual de referência diz que"$@" is equivalent to "$1" "$2" ...

Portanto, com os dois parâmetros fooe bar baz, estes seriam parecidos:

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

(Exceto se os parâmetros contiverem caracteres especiais em vez de apenas strings simples, eles não serão expandidos novamente após a expansão $@e $1...)

Mas, mesmo se considerarmos $@substituídos pelos parâmetros entre aspas, as aspas não estariam lá para echoserem vistas, da mesma forma que gcctambém não obtém as aspas.

<<<é uma exceção à regra "$@"== "$1" "$2" ..., é explicitamente mencionado que, The result is supplied as a single string to the command on its standard inputdepois de passar pela expansão de parâmetros e variáveis ​​e remoção de cotação, entre outros. Então, como sempre, <<< "foo"apenas fornece foocomo entrada, da mesma maneira que somecmd "foo"apenas fornece foocomo argumento.

Chamando o script como ./test.sh foo "bar baz"[...] eu esperaria foo "bar baz"

Se as citações permanecessem, ainda teria que ser "foo" "bar baz". O shell ou qualquer comando em execução não tem idéia do que era a citação quando o comando foi executado. Ou, se houver alguma citação para falar, a chamada do sistema recebe apenas uma lista de strings e aspas terminadas em nulo, sendo apenas um recurso da linguagem shell. Outros idiomas podem ter outras convenções.

ilkkachu
fonte
0

Uma solução alternativa para o bash

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

O Bash não suporta substituições aninhadas, portanto, graças a /programming/12303974/assign-array-to-variable#12304017 por mostrar como reatribuir uma matriz. Consulte man bash( https://linux.die.net/man/1/bash ) para obter detalhes sobre matrizes, expansão e substituição de padrão (em expansão de parâmetro).

Análise

O Bash coloca os parâmetros da linha de comando como uma matriz em $@

q mantém o caractere de citação.

As aspas duplas em torno da expansão de parâmetros ${ ... }preservam os parâmetros individuais como elementos distintos e envolvê-los ( )permite atribuí-los como uma matriz a uma variável.

/#/$qem um parâmetro, a expansão substitui o início do padrão (como regex ^) pelo caractere de citação.

/%/$qem um parâmetro, a expansão substitui o final do padrão (como regex $) pelo caractere de citação.

Caso de uso: consultando o MySQL para obter uma lista de endereços de email na linha de comando

Existem algumas alterações nas instruções acima para usar um caractere de citação diferente, adicionar vírgulas entre os parâmetros e retirar a vírgula final. E é claro que estou sendo ruim colocando a senha na invocação do mysql. Então me processe.

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
fonte
Observe que a citação com aspas duplas não é muito útil para proteger barras invertidas, $expansões e outras aspas duplas. É por isso que as outras respostas usam aspas simples, enquanto se esforçam para lidar com aspas simples dentro da string, ou usam os próprios recursos do shell para produzir uma cópia citada da string.
precisa saber é
@ilkkachu Devidamente observado! É por isso que (entre outras razões) eu votei todas as respostas anteriores. Também porque eu adicionei este caso de uso. Esperemos que o perfeito não seja o inimigo do bem.
21418 Jeff Jeff