Tester si une commande génère une chaîne vide

237

Comment puis-je tester si une commande génère une chaîne vide?

barp
la source
7
Une commande ne renvoie pas de chaîne (elle renvoie un petit code de sortie entier, généralement 0 en cas de succès et 1 ou 2 en cas d'échec), mais elle peut générer des données, à insérer dans une chaîne.
Basile Starynkevitch
@BasileStarynkevitch c'est la chose que je need.I savoir commande retourne la sortie code.But mes commandes code de sortie est toujours 0.so je dois contrôler la sortie
Barp
1
Vous devriez lire un tutoriel de script Bash tldp.org/LDP/abs/html
Basile Starynkevitch
Joey Hess a un utilitaire ifnepour cela. joeyh.name/code/moreutils
tripleee

Réponses:

309

Auparavant, la question demandait comment vérifier s'il y avait des fichiers dans un répertoire. Le code suivant y parvient, mais consultez la réponse de rsp pour une meilleure solution.


Sortie vide

Les commandes ne renvoient pas de valeurs - elles les produisent. Vous pouvez capturer cette sortie en utilisant la substitution de commande ; par exemple $(ls -A). Vous pouvez tester une chaîne non vide dans Bash comme ceci:

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

Notez que j'ai utilisé -Aplutôt que -a, car il omet les entrées symboliques du répertoire courant ( .) et parent ( ..).

Remarque: Comme indiqué dans les commentaires, la substitution de commandes ne capture pas les sauts de ligne de fin . Par conséquent, si la commande ne produit que des sauts de ligne, la substitution ne capturera rien et le test renverra false. Bien que très peu probable, cela est possible dans l'exemple ci-dessus, car une seule nouvelle ligne est un nom de fichier valide! Plus d'informations dans cette réponse .


Code de sortie

Si vous souhaitez vérifier que la commande s'est terminée avec succès, vous pouvez l'inspecter $?, qui contient le code de sortie de la dernière commande (zéro pour la réussite, non nul pour l'échec). Par exemple:

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

Plus d'infos ici .

Will Vousden
la source
4
Vous pouvez omettre -n , donc ce sera justeif [[ $(ls -A) ]]; then
utilisateur
6
Cette méthode donne false pour les commandes qui ne produisent que des sauts de ligne.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
89

TL; DR

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

Merci à netj pour une suggestion pour améliorer mon original:
if [[ $(ls -A | wc -c) -ne 0 ]]; then ...; fi


C'est une vieille question, mais je vois au moins deux choses qui nécessitent des améliorations ou au moins des clarifications.

Premier problème

Le premier problème que je vois est que la plupart des exemples fournis ici ne fonctionnent tout simplement pas . Ils utilisent les commandes ls -alet ls -Al- qui produisent toutes les deux des chaînes non vides dans des répertoires vides. Ces exemples indiquent toujours qu'il y a des fichiers même quand il n'y en a pas .

Pour cette raison, vous devez utiliser simplement ls -A- Pourquoi quelqu'un voudrait-il utiliser le -lcommutateur qui signifie "utiliser un format de liste longue" alors que tout ce que vous voulez, c'est tester s'il y a une sortie ou non, de toute façon?

La plupart des réponses ici sont donc tout simplement incorrectes.

Deuxième problème

Le deuxième problème est que , même si certaines réponses fonctionnent très bien (ceux qui ne sont pas utiliser ls -alou ls -Almais ls -Aplutôt) ils font tous quelque chose comme ceci:

  1. exécuter une commande
  2. mettre en mémoire tampon toute sa sortie dans la RAM
  3. convertir la sortie en une énorme chaîne d'une seule ligne
  4. comparer cette chaîne à une chaîne vide

Ce que je suggère de faire à la place serait:

  1. exécuter une commande
  2. compter les caractères dans sa sortie sans les stocker
    • ou encore mieux - comptez le nombre de 1 caractère au maximum using head -c1
      (merci à netj d' avoir posté cette idée dans les commentaires ci-dessous)
  3. comparer ce nombre à zéro

Ainsi, par exemple, au lieu de:

if [[ $(ls -A) ]]

J'utiliserais:

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

Au lieu de:

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

J'utiliserais:

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

etc.

Pour les petites sorties, cela peut ne pas être un problème, mais pour les sorties plus grandes, la différence peut être significative:

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

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

Comparez-le avec:

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

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

Et encore mieux:

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

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

Exemple complet

Exemple mis à jour de la réponse de Will Vousden:

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

Mis à jour à nouveau après les suggestions de netj :

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

Mise à jour supplémentaire par jakeonfire :

grepse terminera avec un échec s'il n'y a pas de correspondance. Nous pouvons en profiter pour simplifier légèrement la syntaxe:

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

Jeter les espaces blancs

Si la commande que vous testez peut générer des espaces que vous souhaitez traiter comme une chaîne vide, alors au lieu de:

| wc -c

vous pourriez utiliser:

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

ou avec head -c1:

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

ou quelque chose comme ça.

Résumé

  1. Tout d'abord, utilisez une commande qui fonctionne .

  2. Deuxièmement, évitez le stockage inutile dans la RAM et le traitement de données potentiellement énormes.

La réponse n'a pas précisé que la sortie est toujours petite, donc une possibilité de sortie importante doit également être considérée.

rsp
la source
3
[[ $(... | head -c | wc -c) -gt 0 ]]ou [[ -n $(... | head -c1) ]]sont mieux car wc -cdevraient consommer la sortie entière de la commande.
netj
@netj C'est une très bonne idée. Voir ma réponse mise à jour - j'ai ajouté votre suggestion. Merci beaucoup!
rsp
Des idées sur la façon d'obtenir une sortie?
fahrradflucht
Je pense que l'original (sans tête) est meilleur dans le cas général. Ce n'est pas toujours une bonne idée de tuer la commande dès qu'elle commence à écrire la sortie!
stk
@stk La plupart des programmes sortiront en toute sécurité après avoir reçu un signal de mise à mort.
cambunctious
40
if [ -z "$(ls -lA)" ]; then
  echo "no files found"
else
  echo "There are files"
fi

Cela exécutera la commande et vérifiera si la sortie renvoyée (chaîne) a une longueur nulle. Vous voudrez peut-être vérifier les pages de manuel «test» pour d'autres indicateurs.

Utilisez le "" autour de l'argument en cours de vérification, sinon les résultats vides entraîneront une erreur de syntaxe car il n'y a pas de deuxième argument (à vérifier) ​​donné!

Remarque: cela ls -larevient toujours .et ..donc l'utilisation qui ne fonctionnera pas, voir les pages de manuel ls . De plus, bien que cela puisse sembler pratique et facile, je suppose que cela se cassera facilement. Écrire un petit script / application qui renvoie 0 ou 1 selon le résultat est beaucoup plus fiable!

Veger
la source
Vous préférerez peut-être utiliser à la $(place de la $(
citation inverse
Vous avez raison, il est préférable de toujours les utiliser, bien que nous n'ayons pas besoin de les imbriquer ici.
Veger
23

Pour ceux qui veulent une solution élégante et indépendante de la version bash (en fait, devraient fonctionner dans d'autres shells modernes) et ceux qui aiment utiliser des lignes simples pour des tâches rapides. Et c'est parti!

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

(notez que l'un des commentaires mentionnés, ls -alet en fait, juste -let -aretournera tous quelque chose, donc dans ma réponse j'utilise simplels

Alex
la source
5
Si vous utilisez à la grep -q ^place, les caractères de nouvelle ligne seront également mis en correspondance et grep n'imprimera rien sur la sortie standard. De plus, il quittera dès qu'il recevra une entrée, plutôt que d'attendre la fin de son flux d'entrée.
mortehu
Devrait commencer par ls -Asi vous souhaitez inclure des fichiers dot (ou répertoires)
wrlee
13

Manuel de référence Bash

6.4 Expressions conditionnelles Bash

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

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

Vous pouvez utiliser la version abrégée:

if [[ $(ls -A) ]]; then
  echo "there are files"
else
  echo "no files found"
fi
utilisateur
la source
1
Il manque un -z ou -n dans les parenthèses [[...]].
71GA
4
@ 71GA: C'est peut-être facile à ignorer, mais si vous regardez attentivement, vous verrez que ce stringn'est qu'un synonyme de -n string.
utilisateur
10

Comme l'a commenté Jon Lin, ls -alsera toujours affiché (pour .et ..). Vous voulez ls -Aléviter ces deux répertoires.

Vous pouvez par exemple mettre la sortie de la commande dans une variable shell:

v=$(ls -Al)

Une notation plus ancienne et non emboîtable est

v=`ls -Al`

mais je préfère la notation emboîtable $(...)

Vous pouvez tester si cette variable n'est pas vide

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

Et vous pouvez combiner les deux if [ -n "$(ls -Al)" ]; then

Basile Starynkevitch
la source
5

Je suppose que vous voulez la sortie de la ls -alcommande, donc en bash, vous auriez quelque chose comme:

LS=`ls -la`

if [ -n "$LS" ]; then
  echo "there are files"
else
  echo "no files found"
fi
Jon Lin
la source
Cela ne fonctionne pas: la commande lsn'est jamais en cours d'exécution. Vous vérifiez uniquement si l' expansion de la variable LS n'est pas vide.
gniourf_gniourf
1
@gniourf_gniourf - qu'est-ce qui vous fait penser cela? L'incantation de backtick n'est certes pas aussi jolie ou flexible que $ (), mais cela fonctionne ...
tink
Le -acommutateur ne renverra jamais aucun contenu (c'est-à-dire qu'il renverra du contenu, qu'il y ait des fichiers ou non) car il y a toujours les répertoires implicites .et ..présents.
wrlee
3

Voici une solution pour les cas les plus extrêmes:

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

Cela fonctionnera

  • pour tous les obus Bourne;
  • si la sortie de la commande est entièrement composée de zéros;
  • efficacement quelle que soit la taille de sortie;

cependant,

  • la commande ou ses sous-processus seront supprimés une fois que quelque chose sera sorti.
Sec
la source
1

Toutes les réponses données jusqu'à présent concernent les commandes qui terminent et produisent une chaîne non vide.

La plupart sont brisés dans les sens suivants:

  • Ils ne traitent pas correctement les commandes ne produisant que des sauts de ligne;
  • à partir de Bash≥4.4, la plupart spammeront l'erreur standard si la commande génère des octets nuls (car ils utilisent la substitution de commande);
  • la plupart ralentiront le flux de sortie complet, donc attendront que la commande se termine avant de répondre. Certaines commandes ne se terminent jamais (essayez, par exemple, yes).

Donc, pour résoudre tous ces problèmes et répondre efficacement à la question suivante,

Comment puis-je tester si une commande génère une chaîne vide?

vous pouvez utiliser:

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

Vous pouvez également ajouter un certain délai afin de tester si une commande génère une chaîne non vide dans un délai donné, en utilisant readl' -toption de. Par exemple, pour un délai de 2,5 secondes:

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

Remarque. Si vous pensez que vous devez déterminer si une commande génère une chaîne non vide, vous avez très probablement un problème XY.

gniourf_gniourf
la source
Je pense que cette réponse est plutôt sous-estimée. Je teste la performance quelques - unes des solutions ici en utilisant 100 itérations pour chacun des 3 scénarios d'entrée ( seq 1 10000000, seq 1 20et printf""). Le seul affrontement que cette approche de lecture n'a pas gagné était avec l' -z "$(command)"approche pour une entrée vide. Même alors, il ne perd qu'environ 10 à 15%. Ils semblent se rompre seq 1 10. Quoi qu'il en soit, l' -z "$(command)"approche devient si lente que la taille d'entrée augmente que j'éviterais de l'utiliser pour toute entrée de taille variable.
abathur
0

Voici une approche alternative qui écrit les std-out et std-err de certaines commandes dans un fichier temporaire, puis vérifie si ce fichier est vide. Un avantage de cette approche est qu'elle capture les deux sorties et n'utilise pas de sous-coques ou de tuyaux. Ces derniers aspects sont importants car ils peuvent interférer avec le trapping de la gestion des sorties bash (par exemple ici )

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
la source
0

Parfois, vous souhaitez enregistrer la sortie, si elle n'est pas vide, pour la passer à une autre commande. Si oui, vous pouvez utiliser quelque chose comme

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

De cette façon, la rmcommande ne se bloquera pas si la liste est vide.

Scott C Wilson
la source