Pergunta
Encontrei o seguinte trecho:
sh -c 'some shell code' sh …
(onde …
denota zero ou mais argumentos adicionais).
Eu sei que o primeiro sh
é o comando. Eu sei sh -c
que deve executar o código shell fornecido (ou seja some shell code
). Qual é o objetivo do segundo sh
?
Isenção de responsabilidade
Às vezes, uma pergunta semelhante ou relacionada aparece como uma pergunta de acompanhamento depois de sh -c
ser usada adequadamente em uma resposta e o autor da pergunta (ou outro usuário) deseja saber em detalhes como a resposta funciona. Ou pode ser parte de uma questão maior do tipo "o que esse código faz?". O objetivo da pergunta atual é fornecer uma resposta canônica abaixo.
A questão principal, questões semelhantes ou relacionadas abordadas aqui são:
- Qual é o segundo
sh
emsh -c 'some shell code' sh …
? - Qual é o segundo
bash
embash -c 'some shell code' bash …
? - O que está
find-sh
emfind . -exec sh -c 'some shell code' find-sh {} \;
? - Se
some shell code
estivesse em um script de shell e chamássemos./myscript foo …
,foo
seria referido como$1
dentro do script. Massh -c 'some shell code' foo …
(oubash -c …
) refere-se afoo
como$0
. Por que a discrepância? O que há de errado em usar
sh -c 'some shell code' foo …
wherefoo
is um argumento "aleatório"? Em particular:sh -c 'some shell code' "$variable"
sh -c 'some shell code' "$@"
find . -exec sh -c 'some shell code' {} \;
find . -exec sh -c 'some shell code' {} +
Quero dizer, posso usar
$0
em vez de$1
dentrosome shell code
, não me incomoda. O que de ruim pode acontecer?
Algumas das opções acima podem ser consideradas duplicatas (possivelmente duplicatas entre sites) de perguntas existentes (por exemplo , esta ). Ainda não encontrei uma pergunta/resposta que vise explicar o problema para iniciantes que desejam entender sh -c …
e seu argumento extra supostamente inútil observado em respostas de alta qualidade. Esta pergunta preenche a lacuna.
Nota preliminar
É bastante incomum ver
sh -c 'some shell code'
invocados diretamente de um shell. Na prática, se você estiver em um shell, provavelmente escolherá usar o mesmo shell (ou seu subshell) para executarsome shell code
. É bastante comum versh -c
invocados de dentro de outra ferramenta comofind -exec
.No entanto, a maior parte desta resposta é elaborada
sh -c
como um comando autônomo (o que é) porque o problema principal depende exclusivamente desh
. Mais tarde, use alguns exemplos e dicasfind
onde parecer útil e/ou educativo.Resposta básica
É uma string arbitrária. Sua finalidade é fornecer um nome significativo para usar em mensagens de aviso e erro. Aqui está,
sh
mas pode serfoo
,shell1
ouspecial purpose shell
(corretamente entre aspas para incluir espaços).Bash e outros shells compatíveis com POSIX funcionam da mesma maneira quando se trata de arquivos
-c
. Embora eu ache a documentação do POSIX muito formal para citar aqui, um trecho deman 1 bash
é bastante direto:No nosso caso
some shell code
é ocommand_string
e este segundosh
é "o primeiro argumento depois". Ele é atribuído$0
no contexto desome shell code
.Desta forma, o erro de
sh -c 'nonexistent-command' "special purpose shell"
é:e você sabe imediatamente de qual shell ele vem. Isso é útil se você tiver muitas
sh -c
invocações. "O primeiro argumento após ocommand_string
" pode não ser fornecido; nesse caso,sh
(string) será atribuído$0
se o shell forsh
,bash
se o shell forbash
. Portanto, são equivalentes:Mas se você precisar passar pelo menos um argumento depois
some shell code
(ou seja, talvez argumentos que devam ser atribuídos a$1
,$2
, …) então não há como omitir aquele que será atribuído a$0
.Discrepância?
Normalmente, um shell que interpreta um script obtém o nome do script (por exemplo
./myscript
, ) como a entrada zero na matriz de argumentos, disponível posteriormente como$0
. Em seguida, o nome será usado em mensagens de aviso e erro. Normalmente, esse comportamento é perfeitamente aceitável e não há necessidade de fornecê-lo$0
manualmente. Por outro ladosh -c
, não há script para obter um nome. Ainda assim, algum nome significativo é útil, daí a maneira de fornecê-lo.A discrepância desaparecerá se você parar de considerar o primeiro argumento depois
some shell code
como um (tipo de) parâmetro posicional para o código. Sesome shell code
estiver em um script nomeadomyscript
e você chamar./myscript foo …
, o código equivalente comsh -c
será:Aqui
./myscript
está apenas uma string, parece um caminho, mas esse caminho pode não existir; a string pode ser diferente em primeiro lugar. Dessa forma, o mesmo código shell pode ser usado. O shell será atribuídofoo
a$1
em ambos os casos. Nenhuma discrepância.Armadilhas de tratar
$0
como$1
Em muitos casos, isso funcionará. No entanto, existem argumentos contra esta abordagem.
A armadilha mais óbvia é que você pode receber avisos ou erros enganosos do shell invocado. Lembre-se de que eles começarão com o que for
$0
expandido no contexto do shell. Considere este trecho:O erro é:
e você pode se perguntar se
foo
foi tratado como um comando. Não é tão ruim sefoo
for codificado e exclusivo; pelo menos você sabe que o erro tem algo a ver comfoo
, então chama sua atenção para esta mesma linha de código. Pode ser pior:A saída:
A primeira reação é: o que aconteceu com meu diretório pessoal? Outro exemplo:
Saída possível:
É muito fácil pensar que há algo errado com
/etc/fstab
; ou se perguntar por que o código quer interpretá-lo como here-document.Agora execute esses comandos e veja a precisão dos erros quando fornecemos nomes significativos:
some shell code
não é idêntico ao que seria em um script. Isso está diretamente relacionado à alegada discrepância elaborada acima. Pode não ser um problema; ainda em algum nível de shell-fu, você pode apreciar a consistência.Da mesma forma, em algum nível, você pode gostar de escrever scripts da maneira certa. Então, mesmo que você consiga usar
$0
, você não fará isso porque não é assim que as coisas deveriam funcionar.Se você deseja passar mais de um argumento ou se o número de argumentos não é conhecido antecipadamente e você precisa processá-los em sequência, usar
$0
para um deles é uma má ideia.$0
é por design diferente de$1
ou$2
. Este fato se manifestará sesome shell code
usar um ou mais dos seguintes:$#
– O número de parâmetros posicionais não leva$0
em consideração porque$0
não é um parâmetro posicional.$@
ou$*
–"$@"
é como"$1", "$2", …
, não há"$0"
nesta sequência.for f do
(equivalente afor f in "$@"; do
) –$0
nunca é atribuído a$f
.shift
(shift [n]
em geral) – Os parâmetros posicionais são alterados,$0
permanecem intactos.Em particular, considere este cenário:
Você começa com um código assim:
Você percebe que ele executa um
sh
por arquivo. Isso é abaixo do ideal.Você sabe que
-exec … \;
substitui{}
por um nome de arquivo, mas-exec … {} +
substitui{}
possivelmente por muitos nomes de arquivo. Você aproveita o último e introduz um loop:Essa otimização é uma coisa boa. Mas se você começar com isso:
e transformá-lo nisso:
então você introduzirá um bug: o primeiro arquivo vindo da expansão de
{}
não será processado porsome shell code referring "$f"
. Note-exec sh -c … {} +
é executadosh
com tantos argumentos quanto possível, mas há limites para isso e se houver muitos, muitos arquivos, então umsh
não será suficiente, outrosh
processo será gerado porfind
(e possivelmente outro, e outro, …). Com cada umsh
, você pulará (ou seja, não processará) um arquivo.Para testar isso na prática, substitua a string
some shell code referring
porecho
e execute os trechos de código resultantes em um diretório com poucos arquivos. O último snippet não será impresso.
.Tudo isso não significa que você não deva usar
$0
.some shell code
Você pode e deve usar$0
para coisas para as quais foi projetado. Por exemplo, se você desejasome shell code
imprimir um aviso ou erro (personalizado), faça a mensagem começar com$0
. Forneça um nome significativo depoissome shell code
e aproveite os erros significativos (se houver) em vez dos vagos ou enganosos.Dicas finais:
Com
find … -exec sh -c …
nunca embutir{}
no código shell .Pelas mesmas razões
some shell code
, não deve conter fragmentos expandidos pelo shell atual, a menos que você realmente saiba que os valores expandidos são seguros com certeza. A melhor prática é colocar aspas simples em todo o código (como nos exemplos acima, é sempre'some shell code'
) e passar cada valor não fixo como um argumento separado. Tal argumento é recuperável com segurança de um parâmetro posicional dentro do shell interno. A exportação de variáveis também é segura. Execute isso e analise a saída de cada umsh -c …
(a saída desejada éfoo';date'
):Se você executar
sh -c 'some shell code' …
em um shell, o shell removerá aspas simples abraçandosome shell code
; então o shell interno (sh
) irá analisarsome shell code
. É importante citar direito também neste contexto. Você pode achar isso útil: Expansão de parâmetros e aspas dentro de aspas .