Há dois casos em que não entendo por que o bash se comporta dessa forma em relação à verificação de sintaxe e à solicitação de uma nova linha.
Caso 1
O bash pode executar a linha ( (ls) | cat)
, mas dependendo de quando você pressiona Enter ao digitar, pode funcionar ou não.
Isso funciona:
( (ls ) |⏎
cat
Isto falha:
( (ls) ⏎
| cat)
com erro
-bash: erro de sintaxe próximo ao token inesperado `|'
Existe uma lógica para o segundo caso não funcionar? Ou é apenas como o bash funciona internamente?
Caso 2
Outra coisa que não entendo é que, quando você entra, (ls) "⏎
o bash pede outra linha. Existe alguma maneira de finalizar esse comando sem obter um erro de sintaxe? E se não, por que o bash não imprime diretamente uma mensagem de erro para um erro de sintaxe?
Você pode querer pensar um pouco sobre isso em termos de "o que o programa faz quando escaneia o que eu enviei pressionando enter". Em alguns casos, ele inicia uma nova "coisa" (como o
c
incat
: que é claramente o começo de alguma "palavra" que o shell deve entender), alguns caracteres apenas continuam a "coisa" existente (como oc
ongoing thec
-started "thing"), e algumas coisas terminam "coisas" iniciadas anteriormente e as colocam na pilha de "coisas" finalizadas (como o espaço depois decat
, deixando claro que a "coisa"cat
não pode continuar).As "coisas" acima seriam chamadas de "tokens" na ciência do analisador sintático/lexer.
É assim que a sintaxe do shell funciona, sim; ter um parêntesis aberto e sem correspondência não inicia um token "que absorve nova linha". No entanto,
|
inicia um novo token que absorve todos os espaços em branco e novas linhas depois.Então, a "lógica" é: é assim que o shell é definido para funcionar! Desculpe.
sua
"
entrada inicia uma literal de string , então até você digitar outra"
, tudo que returnvocê digitar se tornará apenas quebras de linha naquela literal de string.Aqui:
os subshells são apenas uma distração. Considere o caso mais simples:
O primeiro é um pipeline com
ls
no lado esquerdo, ecat
no lado direito. A sintaxe do shell permite uma nova linha após o|
. (E provavelmente permite, já que sempre foi feito assim. Além disso, não é de forma alguma inequívoco, tudo o que pode vir depois do|
é outro comando.)O segundo é o comando
ls
, seguido pelo broken pipeline| cat
. Eu digo broken pipeline, porque o operador pipeline|
requer um comando no lado esquerdo também. Aqui, não há nada que você possa fazer para corrigir esse erro de sintaxe, então, por exemplo, um shell interativo reclama sobre isso imediatamente, sem esperar por outra linha (mesmo se você o tivesse encapsulado em( )
).Em
o shell não chega ao estágio de realmente verificar a sintaxe do comando. Em termos de sintaxe, a string entre aspas é um único item, e o shell tem que esperar que ela termine antes de poder reconhecer o token correspondente e verificar a sintaxe. Mas sim, não parece que nada na linha a seguir poderia tornar o comando uma sintaxe de shell válida, já que você não pode colocar uma palavra simples depois de um comando composto de subshell.
(você poderia ter algo como
"foo"abc'bar'
, com mais de uma parte entre aspas ou sem aspas, produzindo no total apenas um token de "palavra", mas você entendeu.)Todas as respostas e comentários acima são bons, mas estou surpreso que ninguém mencionou o "personagem de continuação".
Concordo que os
( )
sub-shells são uma distração, você pode "consertar" seu código quebrado adicionando mais um caractere,Torna-se
Sua outra versão
funciona porque o shell vê o
|
char como um token independente (conforme explicado na resposta de MM) e então permite que ele seja o último item em uma linha, mas esperará por uma segunda linha para fechar a leitura da entrada. (Provavelmente há uma explicação técnica melhor, mas essa é a ideia geral).Observe que o
\
char deve ser o último caractere da linha. Qualquer caractere de espaço/tabulação seguinte gerará uma mensagem de erro de sintaxe.shellcheck é uma boa ferramenta para detectar e explicar erros de sintaxe. Neste caso, ele diz: