Eu tenho o seguinte script:
#!/bin/bash
KID3=$(command -v kid3-cli)
ARG1="-c 'get'"
file="'1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'"
echo Command:
echo "$KID3" "$ARG1" "$file"
echo The kid3-cli output:
"$KID3" "$ARG1" "$file"
Isso produz o seguinte:
Command:
/usr/bin/kid3-cli -c 'get' '1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'
The kid3-cli output:
-c 'get', '1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac' does not exist
Observe a vírgula na saída do kid3-cli. Tentei usar elogios em vez de aspas (ou seja ${KID3} ${ARG1} ${file}
), mas isso não parece fazer nenhuma diferença. Se eu usar múltiplas variáveis de comando ($ARG1, $ARG2, etc.), cada uma delas será seguida por uma vírgula na saída kid3-cli.
Quando copio e colo o comando ( /usr/bin/kid3-cli -c 'get' '1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'
) em um shell bash interativo, ou seja, no prompt de comando, ele funciona.
De onde vêm essas vírgulas na saída do kid3-cli, o que estou perdendo e o que devo fazer para corrigir isso?
O comando de trabalho
Um bom ponto de partida é entender o que acontece aqui; então é mais fácil perceber que em outros casos algo mais acontece.
Seu comando é:
O shell divide o comando em palavras, usa espaços e tabulações sem escape e sem aspas como delimitadores. As aspas simples informam ao shell que
get
é uma palavra única (seria uma palavra única mesmo sem aspas) e que1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac
é uma palavra única ( não seria uma palavra única sem aspas). Com efeito, as palavras são:/usr/bin/kid3-cli
-c
get
1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac
Observe que as aspas fizeram seu trabalho e foram removidas.
Em seguida, o shell é executado
/usr/bin/kid3-cli
e fornece exatamente três argumentos-c
:get
e1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac
.kid3-cli
não é possível saber se e onde as aspas foram usadas. Aparentemente, os argumentos da árvore são uma tupla que a ferramenta pode entender sem erros.O comando problemático
Agora isso:
Uma variável não-array entre aspas duplas sempre se expande para exatamente uma palavra (a menos que seja um erro, por exemplo, por causa
set -u
da variável não estar definida). Essas três variáveis serão expandidas para exatamente três palavras:/usr/bin/kid3-cli
da expansão de"$KID3"
-c 'get'
da expansão de"$ARG1"
'1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'
da expansão de"$file"
As cotações que aparecem nas variáveis expandidas não são especiais. Eles podem ser especiais mais tarde, quando (se!) você disser ao shell (ou outro shell, ou qualquer outro) para analisar o resultado novamente (exemplo:
eval
). Aqui você não precisa e não precisa dizer ao shell para analisar o resultado novamente; além disso, fazer isso seria uma má prática.O shell é executado
/usr/bin/kid3-cli
e fornece exatamente dois argumentos:-c 'get'
e'1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'
. Não apenas-c
eget
estão dentro de um único argumento; também as aspas simples chegam akid3-cli
. Aparentemente isso não é algo que a ferramenta consiga entender da maneira que você desejava.A respeito
echo
?echo
funciona imprimindo todos os seus argumentos (exceto argumentos interpretados como opções) literalmente, com um único caractere de espaço entre cada argumento que não seja o último e o próximo; mais, por padrão, um caractere de nova linha à direita.echo "$KID3" "$ARG1" "$file"
resulta nas seguintes palavras:echo
/usr/bin/kid3-cli
da expansão de"$KID3"
-c 'get'
da expansão de"$ARG1"
'1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'
da expansão de"$file"
O shell é executado
echo
e fornece exatamente três argumentos/usr/bin/kid3-cli
:-c 'get'
e'1-01 - Johann Strauss - __Waldmeister___ Ouverture.flac'
. Você vê:onde alguns espaços vêm de variáveis, outros espaços vêm
echo
. Observe que a saída seria idêntica seecho
obtivesse/usr/bin/kid3-cli
,-c
,'get'
,'1-01
,-
,Johann Strauss
,-
, e__Waldmeister___ Ouverture.flac'
(8 argumentos) ou seecho
obtivesse tudo como apenas um argumento longo.Se você anexar
echo
o seu comando de trabalho, ou seja, se você fornecer argumentos que sejam as palavras do comando de trabalho, ou seja, se você executar:então a saída será:
novamente sem qualquer indicação de quais espaços foram adicionados por
echo
.echo
não é uma boa ferramenta para "visualizar" argumentos. Pessoalmente prefiroprintf '<%s>\n'
. No seu casoprintf '<%s>\n' "$KID3" "$ARG1" "$file"
imprimiria:e ficaria bem claro
-c 'get'
que é um argumento único e as aspas simples chegam aprintf
.Ainda há ambiguidade: se um argumento fosse
/usr/bin/kid3-cli>\n<-c 'get'
(onde\n
denota um caractere de nova linha aqui, mas quero dizer um caractere de nova linha literal dentro do argumento), então myprintf
o mostraria como argumentos separados:/usr/bin/kid3-cli
e-c 'get'
. Ainda assim, discussões>\n<
internas são raras (pelo menos no meu fluxo de trabalho; ajuste<%s>\n
às suas necessidades).Sem citação
$ARG1
Se você definir:
e se você executar:
então isso funcionaria (com o padrão
$IFS
). Sem aspas$ARG1
seria dividido em duas palavras:-c
eget
. Observe que se você quiserkid3-cli
verget
(não'get'
), não poderá armazenar-c 'get'
dentro deARG1
. Da mesma formafile="'…'"
também estaria errado aqui.Sem aspas
$ARG1
não é tão ruim se o valor da variável for estático, totalmente sob seu controle, sem caracteres que acionem a geração do nome do arquivo (globbing), o$IFS
que resulta na divisão do valor exatamente onde você deseja. Em geral você deve sempre citar .O principal é que as variáveis do shell servem para armazenar dados, não para armazenar código do shell. Ao
/usr/bin/kid3-cli -c 'get' …
digitar, as aspas simples são sintaticamente significativas para o shell, assim como o espaço logo após-c
. Essas partes são código shell e armazená-las em uma variável é errado. Às vezes (como aqui) você pode armazenar espaços significativos e não citar a variável, ainda é uma prática ruim.Outras ideias aqui: Como podemos executar um comando armazenado em uma variável?
Solução
(Não é a única solução, veja a pergunta já vinculada . Uma solução confiável, segura e elegante o suficiente para se adequar à estrutura do seu código.)
No Bash você pode usar um array para armazenar mais de uma palavra de forma confiável:
As aspas simples
get
não são necessárias, só queria mostrar que elas podem estar lá."${ARG1[@]}"
será expandido para duas palavras:-c
eget
.Com essa abordagem, você também pode usar apenas um array construído gradualmente:
(as aspas que abrangem
1-01 … ….flac
são importantes). Ou use um array que você constrói em uma única etapa:Em cada caso, o comando resultante será equivalente a:
e a menos que você tenha um bom motivo para usar as variáveis, você deve apenas executar o procedimento acima, provavelmente mesmo sem a palavra
command
.