Variável com aspas "$ ()"

12

Eu escrevi este script:

#!/bin/bash
while [ true ] 
do
    currentoutput="$(lsusb)"
    if [ "$currentoutput" != "$lastoutput" ]
    then
        echo "" date and Time >> test.log
        date +%x_r >> test.log
        lastoutput="$(lsusb)"
        lsusb >> test.log
    fi
    sleep 5
done

Sou novato tentando aprender rápido e tenho uma pergunta sobre as aspas da variável .

Coloque uma variável entre $ (), entendo, mas por que as aspas são necessárias mesmo na ifdeclaração? É para fazer um comando aninhado?

Shankhara
fonte
5
Observe que while [ true ]produz um loop infinito, mas talvez não pelo motivo que você pensa; while [ false ] também produz um loop infinito, porque com um único argumento [ ... ]é bem-sucedido se esse argumento for uma sequência não vazia. while truena verdade, executará um comando chamado true(que sempre é bem-sucedido).
chepner
4
Você não coloca uma variável dentro $(). Você coloca um comando dentro $().
Curinga

Respostas:

22

As aspas impedem a "divisão de palavras". Ou seja: dividir variáveis ​​em vários itens em caracteres de espaço em branco (ou, para ser mais exato, em espaços, guias e novas linhas, conforme definido no valor da $IFSvariável de shell padrão ).

Por exemplo,

$ var="one two"
$ howmany(){ echo $#;  }
$ howmany $var
2
$ howmany "$var"
1

Aqui, definimos a howmanyfunção que nos permite saber quantos parâmetros posicionais são dados. Como você pode ver, há dois itens sendo passados ​​para a variável e, com as aspas, o texto na variável é tratado como uma unidade.

Isso é importante para a passagem precisa de informações. Por exemplo, se a variável contiver o caminho para o arquivo e o nome do arquivo contiver espaços em qualquer lugar do caminho, o comando que você está tentando executar poderá falhar ou fornecer resultados imprecisos. Se tentássemos criar um arquivo com a $varvariável, touch $varcriaríamos dois arquivos, mas touch "$var"apenas um.

O mesmo vale para sua [ "$currentoutput" != "$lastoutput" ]parte. Este teste específico realiza uma comparação em duas seqüências de caracteres. Quando o teste é executado, o [comando precisa ver três argumentos - uma sequência de texto, o !=operador e outra sequência de texto. Manter aspas duplas evita a divisão de palavras, e o [comando vê exatamente esses 3 argumentos. Agora, o que acontece se as variáveis ​​não estiverem entre aspas?

$ var="hello world"
$ foo="hi world"
$ [ $var != $foo ]
bash: [: too many arguments
$ 

Aqui, a divisão da palavra ocorre e, em vez disso, ele [vê duas cadeias helloe é worldseguido por !=, seguido por duas outras cadeias hi world. O ponto principal é que, sem aspas duplas, o conteúdo das variáveis ​​é entendido como unidades separadas, em vez de um item inteiro.

Atribuir substituição de comando não requer aspas duplas, como em

var=$( df )

onde você tem a dfsaída do comando salva var. No entanto, é um bom hábito sempre colocar aspas variáveis ​​e substituir comandos, a $(...)menos que você realmente queira que a saída seja tratada como itens separados.


Em uma nota lateral, o

while [ true ]

parte pode ser

while true

[é um comando que avalia seus argumentos e [ whatever ]sempre é verdadeiro, independentemente do que está dentro. Por outro lado, while trueusa o comando trueque sempre retorna o status de saída com êxito (e é exatamente isso que o whileloop precisa). A diferença é um pouco mais de clareza e menos testes realizados. Como alternativa, você também pode usar em :vez detrue

As aspas duplas em echo "" date and Timeparte provavelmente poderiam ser removidas. Eles apenas inserem uma string vazia e adicionam espaço extra à saída. Se isso for desejado, fique à vontade para mantê-los lá, mas não há um valor funcional específico nesse caso.

lsusb >> test.log

Esta parte provavelmente poderia ser substituída por echo "$currentoutput" >> test.log. Não há razão para executar lsusbnovamente depois de já ter sido executado currentoutput=$(lsusb). Nos casos em que novas linhas finais precisam ser preservadas na saída - é possível ver o valor em executar um comando várias vezes, mas no caso de lsusbnão haver necessidade disso. Quanto menos comandos externos você chamar, melhor, porque todas as chamadas para um comando não interno incorrem em custos de CPU, uso de memória e tempo de execução (mesmo que os comandos sejam provavelmente pré-carregados da memória).


Veja também:

Sergiy Kolodyazhnyy
fonte
1
Ótima resposta, se você ainda não o fez, escreva um livro bash. Mas na última parte não deveria echo "$currentoutput" >> test.log ser melhor printf '%s\n' "$currentoutput" >> test.log ?
PLumo
2
@RoVo Sim, printfdeve ser preferido para portabilidade. Como estamos usando o script específico do bash aqui, ele pode ser desculpado echo. Mas a sua observação é muito correto
Sergiy Kolodyazhnyy
1
@SergiyKolodyazhnyy, ... Não tenho certeza de que echoseja totalmente confiável, mesmo quando o shell for conhecido como bash (e o valor dos sinalizadores xpg_echoe posixfor conhecido); se seu valor puder ser -nou -e, ele pode desaparecer em vez de ser impresso.
Charles Duffy
4

No currentoutput="$(lsusb)"lsusb não é uma variável, é um comando. O que essa instrução faz, ele executa o lsusbcomando e atribui sua saída à currentoutputvariável.

A sintaxe mais antiga para isso foi

currentoutput=`lsusb`

você pode encontrá-lo em muitos exemplos e scripts

Para responder a outra parte da sua pergunta, if [ ]é exatamente como a sintaxe para ifé definida no bash. Veja mais em https://www.tldp.org/LDP/Bash-Beginners-Guide/html/sect_07_01.html

marosg
fonte
3
Eu acho que é importante dizer que esse [ ]é realmente o testcomando. Você também pode usar ifinstruções com outros comandos, pois depende de um teste 0 ou diferente de zero do código de saída.
Arronical 12/04/19
... na verdade, é muito melhor executar if grep -qe "somestring" filedo que executar grep -qe "somestring" file; if [ $? = 0 ]; then ..., portanto, a afirmação que if [ ...faz parte da definição de ifsintaxe não é apenas enganosa, mas leva a práticas inadequadas.
Charles Duffy
4

A seguir, executa o comando externo commande retorna sua saída.

"$(command)"

Sem os colchetes / parênteses, isso procuraria uma variável em vez de executar um comando:

"$variable"

Quanto à diferença entre $variablee "$variable", isso se torna relevante quando $variablecontém espaços. Ao usar "$variable", todo o conteúdo da variável será inserido em uma única sequência, mesmo que o conteúdo inclua espaços. Ao usar $variableo conteúdo da variável pode ser expandida em uma lista de argumentos de vários argumentos.

thomasrutter
fonte
HI @thomasrutter, desculpe, eu quis dizer aspas ... Eu edito meu comentário agora!
Shankhara
0

Para ser contrário, o bash recomenda que você use o construtor [[ ... ]]over [ ... ]para evitar ter que citar variáveis ​​no teste e, portanto, os problemas associados à divisão de palavras que os outros apontaram.

[é fornecido no bash para compatibilidade com POSIX com scripts destinados a rodar sob #!/bin/shou aqueles portados para o bash - na maioria das vezes, você deve evitá-lo em favor [[.

por exemplo

# With [ - quotes are needed

$ foo='one two'; bar='one two'; [ $foo = $bar ] && echo "they're equal"
-bash: [: too many arguments

$ foo='one two'; bar='one two'; [ "$foo" = "$bar" ] && echo "they're equal"                                                                                                              
they're equal

# versus [[ - quotes not needed

$ foo='one two'; bar='one two'; [[ $foo = $bar ]] && echo "they're equal"
they're equal
shalomb
fonte
bashnão faz tal recomendação; que fornece [[ ... ]] o que pode ser mais conveniente e tem algumas funcionalidades que [ ... ]não, mas não há nenhum problema com o uso [ ... ] corretamente .
chepner
@chepner - Talvez eu devesse ser mais claro aqui. Claro que não é uma recomendação explícita na página de manual do bash, mas, dado [[que tudo [faz e mais, ainda assim, muitas vezes [desvia as pessoas e, em seguida, tenta atualizar os recursos do [[back to [para falhar e deixar bugs espalhados - é justo dizer que é um a recomendação da comunidade, na medida em que os guias de estilo do bash mais relevantes o aconselharem, nunca use[ .
shalomb