Teste se um comando gera uma sequência vazia

237

Como posso testar se um comando gera uma string vazia?

latir
fonte
7
Um comando não retorna uma sequência de caracteres (ele retorna um pequeno código de saída inteiro, geralmente 0 para êxito e 1 ou 2 em caso de falha), mas pode gerar alguns dados para inserir uma sequência.
Basile Starynkevitch
@BasileStarynkevitch esta é a coisa que eu need.I comando saber retornos saída code.But minha comandos código de saída é sempre 0.so eu preciso para controlar a saída
Barp
1
Você deve ler alguns tutoriais sobre scripts do Bash tldp.org/LDP/abs/html
Basile Starynkevitch
Joey Hess tem um utilitário ifnepara isso. joeyh.name/code/moreutils
tripleee

Respostas:

309

Anteriormente, a pergunta era como verificar se há arquivos em um diretório. O código a seguir consegue isso, mas consulte a resposta da rsp para obter uma solução melhor.


Saída vazia

Os comandos não retornam valores - eles os produzem. Você pode capturar essa saída usando substituição de comando ; por exemplo $(ls -A). Você pode testar uma cadeia de caracteres não vazia no Bash assim:

if [[ $(ls -A) ]]; then
    echo "there are files"
else
    echo "no files found"
fi

Note que eu usei -Aem vez de -a, uma vez que omite a corrente simbólica ( .) e pai ( ..entradas de diretório).

Nota: Conforme indicado nos comentários, a substituição de comandos não captura as novas linhas à direita . Portanto, se o comando gerar apenas novas linhas, a substituição não capturará nada e o teste retornará falso. Embora muito improvável, isso é possível no exemplo acima, pois uma única nova linha é um nome de arquivo válido! Mais informações nesta resposta .


Código de saída

Se você deseja verificar se o comando foi concluído com êxito, é possível inspecionar $?, que contém o código de saída do último comando (zero para êxito, diferente de zero para falha). Por exemplo:

files=$(ls -A)
if [[ $? != 0 ]]; then
    echo "Command failed."
elif [[ $files ]]; then
    echo "Files found."
else
    echo "No files found."
fi

Mais informações aqui .

Will Vousden
fonte
4
Você pode omitir -n , então será apenasif [[ $(ls -A) ]]; then
usuário
6
Este método fornece false para comandos que emitem apenas novas linhas.
Ciro Santilli escreveu
89

TL; DR

if [[ $(ls -A | head -c1 | wc -c) -ne 0 ]]; then ...; fi

Agradeço ao netj por uma sugestão para melhorar meu original:
if [[ $(ls -A | wc -c) -ne 0 ]]; then ...; fi


Esta é uma pergunta antiga, mas vejo pelo menos duas coisas que precisam de melhorias ou pelo menos alguns esclarecimentos.

Primeiro problema

O primeiro problema que vejo é que a maioria dos exemplos fornecidos aqui simplesmente não funciona . Eles usam os comandos ls -ale ls -Al- ambos produzem cadeias não vazias em diretórios vazios. Esses exemplos sempre relatam que existem arquivos, mesmo quando não existem .

Por esse motivo, você deve usar apenas ls -A- Por que alguém iria querer usar a -lopção que significa "usar um formato de listagem longo" quando tudo o que você quer é testar se há alguma saída ou não, afinal?

Portanto, a maioria das respostas aqui são simplesmente incorretas.

Segundo problema

O segundo problema é que, enquanto algumas respostas funcionar bem (aqueles que não usam ls -alou ls -Al, mas ls -Aem vez disso) todos eles fazem algo como isto:

  1. executar um comando
  2. Buffer sua saída inteira na RAM
  3. converter a saída em uma enorme cadeia de linha única
  4. compare essa string com uma string vazia

O que eu sugeriria fazer seria:

  1. executar um comando
  2. conte os caracteres em sua saída sem armazená-los
    • ou melhor ainda - conte o número máximo de 1 caractere using head -c1
      (obrigado a netj por postar essa idéia nos comentários abaixo)
  3. compare esse número com zero

Então, por exemplo, em vez de:

if [[ $(ls -A) ]]

Eu usaria:

if [[ $(ls -A | wc -c) -ne 0 ]]
# or:
if [[ $(ls -A | head -c1 | wc -c) -ne 0 ]]

Ao invés de:

if [ -z "$(ls -lA)" ]

Eu usaria:

if [ $(ls -lA | wc -c) -eq 0 ]
# or:
if [ $(ls -lA | head -c1 | wc -c) -eq 0 ]

e assim por diante.

Para saídas pequenas, pode não ser um problema, mas para saídas maiores a diferença pode ser significativa:

$ time [ -z "$(seq 1 10000000)" ]

real    0m2.703s
user    0m2.485s
sys 0m0.347s

Compare com:

$ time [ $(seq 1 10000000 | wc -c) -eq 0 ]

real    0m0.128s
user    0m0.081s
sys 0m0.105s

E ainda melhor:

$ time [ $(seq 1 10000000 | head -c1 | wc -c) -eq 0 ]

real    0m0.004s
user    0m0.000s
sys 0m0.007s

Exemplo completo

Exemplo atualizado da resposta de Will Vousden:

if [[ $(ls -A | wc -c) -ne 0 ]]; then
    echo "there are files"
else
    echo "no files found"
fi

Atualizado novamente após sugestões de netj :

if [[ $(ls -A | head -c1 | wc -c) -ne 0 ]]; then
    echo "there are files"
else
    echo "no files found"
fi

Atualização adicional por jakeonfire :

grepsairá com falha se não houver correspondência. Podemos tirar vantagem disso para simplificar um pouco a sintaxe:

if ls -A | head -c1 | grep -E '.'; then
    echo "there are files"
fi

if ! ls -A | head -c1 | grep -E '.'; then
    echo "no files found"
fi

Descartando espaço em branco

Se o comando que você está testando pode gerar algum espaço em branco que você deseja tratar como uma sequência vazia, em vez de:

| wc -c

você poderia usar:

| tr -d ' \n\r\t ' | wc -c

ou com head -c1:

| tr -d ' \n\r\t ' | head -c1 | wc -c

ou algo assim.

Resumo

  1. Primeiro, use um comando que funcione .

  2. Segundo, evite o armazenamento desnecessário na RAM e o processamento de dados potencialmente enormes.

A resposta não especificou que a saída é sempre pequena; portanto, uma possibilidade de saída grande também deve ser considerada.

rsp
fonte
3
[[ $(... | head -c | wc -c) -gt 0 ]]ou [[ -n $(... | head -c1) ]]são melhores porque wc -cteriam que consumir toda a saída do comando.
22717 netj
@netj Esta é uma ideia muito boa. Veja minha resposta atualizada - eu adicionei sua sugestão. Muito obrigado!
Rsp #
Alguma idéia de como obter a saída?
Fahrradflucht 28/05
Eu acho que o original (sem cabeça) é melhor no caso geral. Nem sempre é uma boa ideia interromper o comando assim que ele começa a escrever a saída!
Stk
@stk A maioria dos programas sai com segurança após receber um sinal de interrupção.
cambunctious
40
if [ -z "$(ls -lA)" ]; then
  echo "no files found"
else
  echo "There are files"
fi

Isso executará o comando e verificará se a saída retornada (string) tem um comprimento zero. Você pode verificar as páginas de manual 'teste' para outros sinalizadores.

Use o "" em volta do argumento que está sendo verificado, caso contrário, resultados vazios resultarão em um erro de sintaxe, pois não há um segundo argumento (para verificar) fornecido!

Nota: isso ls -lasempre retorna .e, ..portanto, usá-lo não funciona, consulte as páginas de manual . Além disso, embora isso possa parecer conveniente e fácil, suponho que ele se quebre facilmente. Escrever um pequeno script / aplicativo que retorne 0 ou 1, dependendo do resultado, é muito mais confiável!

Veger
fonte
Você pode preferir usar $(em vez de crase, porque $(ninho melhor
Basile Starynkevitch
Você está certo, é uma prática recomendada usá-los sempre, embora não precisemos de aninhamento aqui.
Veger 27/08/12
23

Para aqueles que desejam uma solução elegante e independente da versão do bash (de fato, deve funcionar em outros shells modernos) e aqueles que gostam de usar one-liners para tarefas rápidas. Aqui vamos nós!

ls | grep . && echo 'files found' || echo 'files not found'

(observe como um dos comentários mencionados ls -ale, de fato, apenas -le -atodos retornarão algo, por isso, na minha resposta,ls

Alex
fonte
5
Se você usar grep -q ^, os caracteres de nova linha também serão correspondidos e o grep não imprimirá nada na saída padrão. Além disso, ele será encerrado assim que receber qualquer entrada, em vez de aguardar o término do fluxo de entrada.
mortehu 12/06
Deve começar ls -Ase você quiser incluir arquivos de ponto (ou diretórios)
wrlee
13

Manual de Referência do Bash

6.4 Expressões condicionais de bash

-z string
     True if the length of string is zero.

-n string
string
     True if the length of string is non-zero.

Você pode usar a versão abreviada:

if [[ $(ls -A) ]]; then
  echo "there are files"
else
  echo "no files found"
fi
do utilizador
fonte
1
Há um -z ou -n faltando entre parênteses [[...]].
71GA
4
@ 71GA: Talvez isso seja fácil de ignorar, mas se você olhar com cuidado, verá que isso stringé apenas um sinônimo -n string.
usuário
10

Como Jon Lin comentou, ls -alsempre produzirá (para .e ..). Você deseja ls -Alevitar esses dois diretórios.

Você pode, por exemplo, colocar a saída do comando em uma variável do shell:

v=$(ls -Al)

Uma notação mais antiga e não aninhada é

v=`ls -Al`

mas eu prefiro a notação aninhada $(...)

Você pode testar se essa variável não está vazia

if [ -n "$v" ]; then
    echo there are files
else
    echo no files
fi

E você pode combinar tanto quanto if [ -n "$(ls -Al)" ]; then

Basile Starynkevitch
fonte
5

Suponho que você queira a saída do ls -alcomando, portanto, no bash, você terá algo como:

LS=`ls -la`

if [ -n "$LS" ]; then
  echo "there are files"
else
  echo "no files found"
fi
Jon Lin
fonte
Isso não funciona: o comando lsnunca está sendo executado. Você só verifica se a expansão da variável LS não está vazia.
Gniourf_gniourf
1
@gniourf_gniourf - o que faz você pensar isso? O encantamento crase é, reconhecidamente, não tão bonita ou flexível quanto $ (), mas funciona ...
tink
A -aopção nunca retornará conteúdo (isto é, retornará conteúdo, independentemente de haver arquivos ou não), pois sempre há os diretórios implícitos .e ..presentes.
wrlee 18/02
3

Aqui está uma solução para casos mais extremos:

if [ `command | head -c1 | wc -c` -gt 0 ]; then ...; fi

Isso vai funcionar

  • para todas as conchas Bourne;
  • se a saída do comando for todos os zeros;
  • eficientemente, independentemente do tamanho da saída;

Contudo,

  • o comando ou seus subprocessos serão eliminados assim que alguma coisa for emitida.
Curt
fonte
1

Todas as respostas dadas até agora tratam de comandos que terminam e emitem uma sequência não vazia.

A maioria é quebrada nos seguintes sentidos:

  • Eles não lidam corretamente com comandos que produzem apenas novas linhas;
  • a partir do Bash≥4,4, a maioria enviará um erro padrão de spam se o comando gerar bytes nulos (pois eles usam substituição de comando);
  • a maioria consumirá todo o fluxo de saída, portanto esperará até o comando terminar antes de responder. Alguns comandos nunca terminam (tente, por exemplo, yes).

Portanto, para corrigir todos esses problemas e responder com eficiência à seguinte pergunta,

Como posso testar se um comando gera uma string vazia?

você pode usar:

if read -n1 -d '' < <(command_here); then
    echo "Command outputs something"
else
    echo "Command doesn't output anything"
fi

Você também pode adicionar algum tempo limite para testar se um comando gera uma seqüência de caracteres não vazia dentro de um determinado tempo, usando reada -topção 's . Por exemplo, por um tempo limite de 2,5 segundos:

if read -t2.5 -n1 -d '' < <(command_here); then
    echo "Command outputs something"
else
    echo "Command doesn't output anything"
fi

Observação. Se você acha que precisa determinar se um comando gera uma sequência não vazia, é muito provável que tenha um problema XY.

gniourf_gniourf
fonte
Penso que esta resposta é subestimada. I foi o desempenho de testes de algumas das soluções aqui utilizando 100 iterações para cada um dos cenários de entrada (3 seq 1 10000000, seq 1 20, e printf""). A única combinação que essa abordagem de leitura não venceu foi com a -z "$(command)"abordagem de uma entrada vazia. Mesmo assim, perde apenas entre 10 e 15%. Eles parecem se equilibrar seq 1 10. Independentemente disso, a -z "$(command)"abordagem se torna tão lenta quanto o tamanho da entrada aumenta, que eu evitaria usá-la para qualquer entrada de tamanho variável.
Abathur
0

Aqui está uma abordagem alternativa que grava o std-out e std-err de algum comando em um arquivo temporário e, em seguida, verifica se esse arquivo está vazio. Um benefício dessa abordagem é que ela captura as duas saídas e não usa subcascas ou tubos. Esses últimos aspectos são importantes porque eles podem interferir no manuseio de saída da saída do bash (por exemplo, aqui )

tmpfile=$(mktemp)
some-command  &> "$tmpfile"
if [[ $? != 0 ]]; then
    echo "Command failed"
elif [[ -s "$tmpfile" ]]; then
    echo "Command generated output"
else
    echo "Command has no output"
fi
rm -f "$tmpfile"
Darren Smith
fonte
0

Às vezes, você deseja salvar a saída, se não estiver vazia, para passá-la para outro comando. Nesse caso, você poderia usar algo como

list=`grep -l "MY_DESIRED_STRING" *.log `
if [ $? -eq 0 ]
then
    /bin/rm $list
fi

Dessa forma, o rmcomando não será interrompido se a lista estiver vazia.

Scott C Wilson
fonte