Como "expandir" uma variável bash (o código incluído funciona para bash, mas não para zsh)

Como "expandir" uma variável bash (o código incluído funciona para bash, mas não para zsh)

Uma resposta comoessefaz um bom trabalho explicando como controlar os passestodosvariáveis ​​até um comando.

Eu queria explorar como fazer isso por argumento. Observe (isso foi testado em zsh):

$ program() { echo 1: $1 2: $2 3: $3; }

$ run() { program "$@"; }

$ run2() { echo `run $1`; }

$ run2 'a b' c
1: a b 2: 3:

Eu quero uma maneira de passar um primeiro argumento que é uma string que contém espaços e ter esse $@estilo emendado em outro comando. por exemplo, run2 "a b" cdeve produzir1: a 2: b 3:

Neste ponto, resolvi meu problema imediato porque, embora meu código de teste seja quebrado quando o testei no zsh, uma vez implementado em um script bash real, ele funciona.

Isso indica que talvez isso dependa de algumas complexidades no tratamento de strings e argumentos que não são "seguras". Portanto, este é mais um pedido de comentário sobre uma maneira mais robusta de alcançar esse comportamento de forma confiável.

Responder1

A diferença entre bash e zsh que importa aqui está na forma como run2chama run, especificamente no efeito de deixar $1sem aspas.

  • No zsh, run $1aplica o operador “remove-if-empty” a $1, ou seja, chama runcom o primeiro argumento que foi passado para run2, exceto que se o primeiro argumento para run2estava vazio (ou se run2foi chamado sem argumento), então runé chamado sem argumento.
  • Em outros shells estilo Bourne, como o bash, run $1aplica o operador “split+glob” a $1, ou seja, divide o primeiro argumento em run2pedaços separados por espaços em branco¹, interpreta cada parte como um padrão curinga² e substitui cada padrão curinga que corresponde a um ou mais arquivo pela lista de correspondências.

Assim, run2 'a b' cchama runcom o argumento a bem zsh (o argumento é passado inalterado), mas chama runcom os dois argumentos ae bem bash (dividido em partes delimitadas por espaços em branco).

Eu quero uma maneira de passar um primeiro argumento que é uma string que contém espaços e ter esse $@estilo emendado em outro comando. por exemplo, run2 "a b" cdeve produzir1: a 2: b 3:

Sua descrição e seu exemplo dizem coisas diferentes. Se você quiser passar o primeiro argumento para o outro comando, use run "$1"para garantir que o argumento não seja dividido. Passar argumentos inalterados é o objetivo do "$@".

Parece que o que você realmente quer fazer é dividir o primeiro argumento run2em pedaços delimitados por espaços em branco. No bash, você pode fazer isso desativando a expansão curinga e depois (supondo que IFSo padrão não seja alterado) usando uma expansão sem aspas.

run2 () ( set -f; run $1; )

( echo "$(somecommand)"é essencialmente equivalente a executar somecommandem um subshell, e parece que é isso que você quis dizer, em vez de echo $(somecommand)aplicar split+glob na saída do comando, então removi a substituição redundante de comando de eco.)

No zsh, você pode usar o =caractere em uma substituição de parâmetro para realizar a divisão mundial (e sem globbing) no valor.

run2 () { run $=1; }

A sintaxe do zsh não é compatível com a do sh simples. Se você deseja obter um script sh do zsh, você pode usar a emulateconstrução:

emulate sh -c '. myscript.sh'

Use emulate kshpara emular (alguns recursos do) ksh. Isso não emula todos os recursos do bash, mas permite usar arrays.

¹ De forma mais geral, com base no valor de IFS.
² A menos que tenha sido desativado com set -f.

Responder2

Bem-vindo ao mundo das citações e das citações surpresas.

O principal problema é que zshnão é dividido em caracteres IFS por padrão.
Nisso: é diferente de todas as outras conchas.

Para testar como as cotações mudam a maneira como os shells funcionam, precisamos de um código que teste versões entre aspas e sem aspas do seu código.

Seu código (adicionando algumas variáveis):

program() { printf '%02d-1: %6s   2: %6s    3: %6s' "$i" "$1" "$2" "$3"; }
runa()  { program  "$@" ; }
run1()  { echo "`runa  $1 $2 $3 `"; }
run1    'a b' c

Deixe-me expandir os detalhes.

Vamos supor que você crie um shlink local apontando para onde zshmora:

ln -s "/usr/bin/zsh" ./sh

E também, vamos supor que você copie o script a seguir para um soarquivo.

Um script repetindo cada função com variáveis ​​entre aspas e sem aspas:

program() { printf '%02d-1: %6s   2: %6s    3: %6s' "$i" "$1" "$2" "$3"; }
runa() { program "$@"; }
runb() { program  $@ ; }

run1() { echo "`runa  "$1" "$2" "$3"`"; }
run2() { echo "`runb  "$1" "$2" "$3"`"; }
run3() { echo "`runa  $1 $2 $3`"; }
run4() { echo "`runb  $1 $2 $3`"; }

for i in `seq 4`; do
    run"$i" 'a b' c
done

Então, na execução, imprimimos isto:

# Any shell (except zsh) results.
01-1:    a b   2:      c    3:
02-1:      a   2:      b    3:      c
03-1:      a   2:      b    3:      c
04-1:      a   2:      b    3:      c

Somente a primeira execução (run1), onde tudo está entre aspas, permanece 'a b'unida.

No entanto, zsh age como se tudo tivesse sido citado o tempo todo:

# ZSH results.
01-1:    a b   2:      c    3:
02-1:    a b   2:      c    3:
03-1:    a b   2:      c    3:
04-1:    a b   2:      c    3:

zsh na emulação.

Supõe-se que zshemulará shells mais antigos se for chamado como shou ksh.
Mas na prática isso nem sempre é verdade:

$ ./sh ./so   # emulated sh
01-1:    a b   2:      c    3:
02-1:    a b   2:      c    3:
03-1:      a   2:      b    3:      c
04-1:      a   2:      b    3:      c

A segunda linha é diferente da segunda linha de qualquer outro shell.

É uma boa ideialeia as respostas para esta pergunta

informação relacionada