Comment fusionner et canaliser les résultats de deux commandes différentes en une seule commande?

35

Je veux fusionner (union) la sortie de deux commandes différentes et les diriger vers une seule commande.

Un exemple idiot:

Commandes Je veux fusionner la sortie:

cat wordlist.txt
ls ~/folder/*

dans:

wc -l

Dans cet exemple, si wordlist.txt contient 5 lignes et 3 fichiers, je souhaite wc -lrenvoyer 8.

$cat wordlist.txt *[magical union thing]* ls ~/folder/* | wc -l
8

Comment je fais ça?

David Oneill
la source

Réponses:

56

Votre union magique est un point-virgule ... et des accolades:

    { cat wordlist.txt ; ls ~/folder/* ; } | wc -l

Les accolades ne regroupent que les commandes, de sorte que le signe de canal |affecte la sortie combinée.

Vous pouvez également utiliser des parenthèses ()autour d'un groupe de commandes pour exécuter les commandes d'un sous-shell. Cela a un ensemble subtil de différences avec les accolades, par exemple, essayez ce qui suit:

    cd $HOME/Desktop ; (cd $HOME ; pwd) ; pwd
    cd $HOME/Desktop ; { cd $HOME ; pwd ; } ; pwd

Vous verrez que toutes les variables d'environnement, y compris le répertoire de travail en cours, sont réinitialisées après la sortie du groupe de parenthèses, mais pas après le groupe d'accolades.

En ce qui concerne le point-virgule, les variantes incluent les signes &&et ||, qui exécuteront la deuxième commande de manière conditionnelle uniquement si la première a abouti ou si, dans la négative, par exemple,

    cd $HOME/project && make
    ls $HOME/project || echo "Directory not found."
pablomme
la source
1
Ça marche! Aidez-moi à apprendre - que font exactement les accolades ici? Quels autres pouvoirs magiques ont-ils?
David Oneill
@DavidOneill { list; }est une commande composée . De bash(1): la liste est simplement exécutée dans l'environnement shell actuel. La liste doit se terminer par un saut de ligne ou un point-virgule. [..] Le statut de retour est le statut de sortie de la liste.
Lekensteyn
J'ai ajouté un peu plus d'informations à la réponse. Le guide définitif pour les scripts shell avec bash est la page de manuel bash, tapez à man bashpartir de la ligne de commande et parcourez-la.
Pablomme
devrait le; dans ces commandes, ne pas être && si le fichier wordlist.txt n’existe pas?
Rinzwind
S'il wordlist.txtn'existe pas, une erreur de catapparaîtra sur l'erreur standard, mais pas sur la sortie standard, wc -lelle ne comptera donc aucune ligne. Même chose pour ~/folderne pas exister. On pourrait ajouter 2> /dev/nullentre chacune de ces commandes et leur point-virgule pour éviter les parasites sur les erreurs standard, mais les messages d'erreur ne sont pas moche, mais sont sans danger pour le comptage des lignes.
Pablomme
8

Etant donné qu’il wcaccepte un chemin de fichier en entrée, vous pouvez également utiliser la substitution de processus:

wc -l <(cat wordlist.txt; ls ~/folder/*)

Cela équivaut à peu près à:

echo wordlist.txt > temp
ls ~/folder/* >> temp
wc -l temp

Remarquez que ls ~/folder/*le contenu des sous-répertoires est également renvoyé, le cas échéant (en raison de l’expansion globale). Si vous voulez juste lister le contenu de ~/folder, utilisez simplement ls ~/folder.

Lekensteyn
la source
Le wc -létait juste un composé par exemple, je suis réellement tuyauterie en quelque chose de plus compliqué qui ne dispose pas de cette option.
David Oneill
2
@DavidOneill Eh bien, comme cataccepte un argument de fichier, vous pouvez utiliser cat <(cat wordlist.txt; ls wordlist.txt) | wc -let même cat wordlist.txt <(ls wordlist.txt) | wc -l. C’est bien sûr très moche, mais cela démontre les possibilités infinies des outils en ligne de commande.
Lekensteyn
1
@DavidOneill Correct, je l'ai corrigé maintenant, merci.
Lekensteyn
1
Vous voulez ls ~/folder/que if ~/foldersoit un lien symbolique lsrépertorie le contenu de sa cible au lieu du lien lui-même. Soit dit en passant, toutes les lscommandes doivent être suivies de -1manière à ce qu'un seul fichier par ligne soit imprimé, ce qui wc -lconstitue un moyen valable de compter les fichiers.
Pablomme
@pablomme Vous êtes vrai à propos du lien symbolique, mais cela -1est implicite, car la sortie n'est pas un terminal, mais un tuyau.
Lekensteyn
2

Je me posais la même question et j'ai fini par écrire un court script.

magicalUnionThing(Je l'appelle append):

#!/bin/sh
cat /dev/stdin
$*

Rendre ce script exécutable

chmod +x ./magicalUnionThing

Maintenant tu fais

cat wordlist.txt |./magicalUnionThing ls ~/folder/* | wc -l

Ce qu'il fait:

  • Envoyer une entrée standard vers une sortie standard
  • Exécuter l'argument. $*renvoie tous les arguments sous forme de chaîne. La sortie de cette commande va par défaut à la sortie standard du script.

Ainsi, la sortie standard de magicalUnionThing sera sa sortie stdin + stdout de la commande transmise en tant qu'argument.

Il existe bien sûr des moyens plus simples, comme indiqué dans les autres réponses.
Peut-être que cette alternative peut être utile dans certains cas.

Rolf
la source