Devrais-je me soucier des chats inutiles?

50

De nombreux utilitaires de ligne de commande peuvent prendre leur entrée à partir d'un canal ou d'un argument de nom de fichier. Pour les scripts shell longs, je trouve que le démarrage de la chaîne avec la catrend plus lisible, en particulier si la première commande a besoin d'arguments sur plusieurs lignes.

Comparer

sed s/bla/blaha/ data \
| grep blah \
| grep -n babla

et

cat data \
| sed s/bla/blaha/ \
| grep blah \
| grep -n babla

Cette dernière méthode est-elle moins efficace? Si tel est le cas, la différence est-elle suffisante pour que le script soit exécuté, par exemple une fois par seconde? La différence de lisibilité n'est pas énorme.

Tshepang
la source
30
Je passe beaucoup plus de temps à regarder les gens s’attaquer à propos de l’utilisation inutile de chats sur ce site que mon système ne démarre réellement les processus de chat
Michael Mrozek
4
@Michael: 100% d'accord. Heck, il m'a fallu plus de temps pour me connecter une fois à l'ancien prix Usenet que mon ordinateur ne perdra jamais à instancier cat. Cependant, je pense que la plus grande question ici est la lisibilité du code, qui est souvent une priorité par rapport aux performances. Quand plus vite peut réellement être écrit plus joli , pourquoi pas? Souligner le problème avec catconduit généralement l'utilisateur à une meilleure compréhension des pipelines et des processus en général. Cela en vaut la peine, alors ils écrivent du code compréhensible la prochaine fois.
Caleb
3
J'ai en fait une autre raison pour laquelle je n'aime pas le premier formulaire: si vous souhaitez ajouter une autre commande au début du pipeline, vous devez également déplacer l'argument, de sorte que la modification est plus pénible. (Bien entendu, cela ne signifie pas que vous devez utiliser cat; l'argument de Caleb concernant l'utilisation de fonctions et la redirection résout également ce problème.)
Cascabel le
Connexes: Supprimer les utilisations inutiles du chat ou non?   (Meta)
G-Man dit 'Réintégrez Monica'
1
C'est la soirée au boulot, mon père refuse de travailler. J'ouvre stackoverflow et trouve une question intitulée "Dois-je me préoccuper des chats inutiles?" et voir des animaux sans abri et un programmateur, songeant à les nourrir ou non ...
Boris Burkov

Réponses:

46

La réponse "définitive" vous est bien sûr apportée par The Useless Use of catAward .

Le but de cat est de concaténer (ou "caténer") des fichiers. S'il ne s'agit que d'un seul fichier, le concaténer sans rien est une perte de temps et vous coûte un processus.

Instancier chat juste pour que votre code se lit différemment ne nécessite qu'un processus supplémentaire et un ensemble de flux d'entrée / sortie supplémentaire qui ne sont pas nécessaires. Généralement, le blocage réel de vos scripts sera constitué par des boucles inefficaces et un traitement réel. Sur la plupart des systèmes modernes, un ajout catne va pas nuire à vos performances, mais il existe presque toujours un autre moyen d'écrire votre code.

Comme vous le constatez, la plupart des programmes peuvent accepter un argument pour le fichier d'entrée. Cependant, il existe toujours un shell intégré <qui peut être utilisé partout où un flux STDIN est attendu, ce qui vous permet d'économiser un processus en effectuant le travail dans le processus shell en cours d'exécution.

Vous pouvez même faire preuve de créativité avec O vous écrivez. Normalement, il serait placé à la fin d'une commande avant de spécifier des redirections de sortie ou des tubes comme celui-ci:

sed s/blah/blaha/ < data | pipe

Mais ça ne doit pas forcément être comme ça. Cela peut même venir en premier. Par exemple, votre exemple de code pourrait être écrit comme ceci:

< data \
    sed s/bla/blaha/ |
    grep blah |
    grep -n babla

Si la lisibilité du script vous préoccupe et que votre code est suffisamment en désordre pour que l'ajout d'une ligne catfacilite le suivi, il existe d'autres moyens de nettoyer votre code. L'une des solutions que j'utilise le plus souvent pour faciliter la compréhension des scripts consiste à diviser les canaux en ensembles logiques et à les enregistrer dans des fonctions. Le code de script devient alors très naturel et toute partie de la tôle est plus facile à déboguer.

function fix_blahs () {
    sed s/bla/blaha/ |
    grep blah |
    grep -n babla
}

fix_blahs < data

Vous pouvez ensuite continuer avec fix_blahs < data | fix_frogs | reorder | format_for_sql. Une ligne de commande qui se lit comme cela est vraiment facile à suivre et les composants individuels peuvent être facilement mis au point dans leurs fonctions respectives.

Caleb
la source
26
Je ne savais pas que cela <filepourrait venir avant la commande. Cela résout tous mes problèmes!
3
@ Tim: Bash et Zsh sont d'accord, bien que je trouve ça moche. Quand je crains que mon code soit joli et facile à maintenir, j'utilise généralement des fonctions pour le nettoyer. Voir ma dernière édition.
Caleb
8
@Tim <filepeut arriver n'importe où sur la ligne de commande: <file grep needleou grep <file needleou grep needle <file. L'exception concerne les commandes complexes telles que les boucles et les regroupements; la redirection doit venir après la fermeture done/ }/ )/ etc. @Caleb Ceci est valable pour tous les shells Bourne / POSIX. Et je ne suis pas d'accord pour dire que c'est moche.
Gilles 'SO- arrête d'être méchant'
9
@ Gilles, en bash, vous pouvez remplacer $(cat /some/file)par $(< /some/file), ce qui fait la même chose mais évite de générer un processus.
cjm
3
Juste pour confirmer que la $(< /some/file)portabilité est limitée. Cela fonctionne dans bash, mais pas dans BusyBox ash, par exemple, ni dans FreeBSD sh. Cela ne fonctionne probablement pas au tableau de bord non plus, puisque ces trois derniers obus sont tous des cousins ​​proches.
dubiousjim
22

Voici un résumé de certains des inconvénients de:

cat $file | cmd

plus de

< $file cmd
  • Tout d’abord, une remarque: il y a (intentionnellement pour les besoins de la discussion) des guillemets doubles manquants $file. Dans le cas de cat, c'est toujours un problème sauf pour zsh; dans le cas de la redirection, ce n'est un problème que pour bashou ksh88et, pour certains autres shells, uniquement lorsqu'il est interactif (pas dans des scripts).
  • L'inconvénient le plus souvent cité est le processus supplémentaire engendré. Notez que si cmdest intégré, il y a même 2 processus dans certains shells bash.
  • Toujours sur le plan des performances, sauf dans les shells où catest intégré, une commande supplémentaire est également exécutée (et bien sûr chargée et initialisée (ainsi que les bibliothèques auxquelles elle est liée)).
  • Toujours sur le plan de la performance, pour les gros fichiers, cela signifie que le système devra calendrier en alternance les catet les cmdprocessus et constamment remplir et vider la mémoire tampon de tuyau. Même si le cmdfait de 1GBgrands read()appels système à un moment, le contrôle devra aller et venir entre catet cmdparce qu'un tuyau ne peut pas contenir plus de quelques kilo - octets de données à la fois.
  • Certains cmds (comme wc -c) peuvent faire des optimisations lorsque leur stdin est un fichier normal, ce qu’ils ne peuvent faire cat | cmdcar leur stdin n’est alors qu’un tuyau. Avec catun tuyau, cela signifie également qu'ils ne peuvent pas seek()dans le fichier. Pour des commandes telles que tacou tail, cela fait une énorme différence de performances car cela signifie catqu’elles ont besoin de stocker toute l’entrée en mémoire.
  • La cat $file, et même sa version plus correcte cat -- "$file"ne fonctionnera pas correctement pour certains noms de fichiers spécifiques comme -(ou --helpou quoi que ce soit à partir de -si vous oubliez le --). Si on insiste pour utiliser cat, il devrait probablement utiliser cat < "$file" | cmdplutôt pour la fiabilité.
  • S'il $filene peut pas être ouvert en lecture (l'accès est refusé, n'existe pas ...), < "$file" cmdsignalera un message d'erreur cohérent (par le shell) et ne s'exécutera pascmd , alors qu'il cat $file | cmdsera toujours exécuté cmdmais avec son stdin qui ressemble à un fichier vide. Cela signifie également que, dans des domaines tels que < file cmd > file2, file2n’est pas compressé s’il filene peut pas être ouvert.
Stéphane Chazelas
la source
2
Concernant les performances: ce test montre que la différence est de l'ordre de 1 pct sauf si vous effectuez très peu de traitement sur le flux oletange.blogspot.dk/2013/10/useless-use-of-cat.html
Ole Tange
2
@OleTange. Voici un autre test: truncate -s10G a; time wc -c < a; time cat a | wc -c; time cat a | cat | wc -c. Il y a beaucoup de paramètres qui entrent dans l'image. La pénalité de performance peut aller de 0 à 100%. En tout cas, je ne pense pas que la sanction puisse être négative.
Stéphane Chazelas
2
wc -cest un cas assez unique, car il a un raccourci. Si vous le faites plutôt, wc -wil est comparable à grepmon exemple (c’est-à-dire très peu de traitement - ce qui est la situation où «<» peut faire la différence).
Ole Tange
@OleTange, même ( wc -wsur un fichier fragmenté de 1 Go dans les paramètres régionaux C sous Linux 4.9 amd64), je trouve que l'approche cat prend 23% de temps supplémentaire sur un système multicœur et 5% lorsque les lier à un seul noyau. Affichage de la charge supplémentaire occasionnée par l’accès aux données par plusieurs cœurs. Vous obtiendrez peut-être des résultats différents si vous modifiez la taille du tuyau, utilisez des données différentes, impliquez de véritables E / S, utilisez une implémentation cat qui utilise splice () ... Tout confirmant qu'il y a beaucoup de paramètres entrant dans l'image. et cela catne vous aidera en aucun cas .
Stéphane Chazelas
1
Pour moi avec un fichier de 1 Go wc -wc'est une différence d'environ 2% ... 15% de différence si c'est dans un simple grep. Ensuite, bizarrement, si c'est sur un partage de fichiers NFS, il est en fait 20% plus rapide à le lire s'il est envoyé depuis cat( gist.github.com/rdp/7162414833becbee5919cda855f1cb86 ). Bizarre ...
rogerdpack
16

Mettre <fileen fin de pipeline est moins lisible qu’au cat filedébut. L'anglais naturel se lit de gauche à droite.

Mettre <filele début du pipeline est également moins lisible que cat, je dirais. Un mot est plus lisible qu'un symbole, en particulier un symbole qui semble indiquer le mauvais sens.

Utiliser catpréserve le command | command | commandformat.

Jim
la source
Je suis d'accord, utiliser <une fois rend le code moins lisible, car il détruit la cohérence syntaxique d'un multipipeline.
A.Danischewski
@ Jim Vous pouvez résoudre le problème de la lisibilité en créant un alias <ressemblant à ceci: alias load='<'puis utilisez par exemple load file | sed .... Les alias peuvent être utilisés dans les scripts après exécution shopt -s expand_aliases.
niieani
1
Oui, je connais les pseudonymes. Cependant, bien que cet alias remplace le symbole par un mot, il oblige le lecteur à connaître votre paramétrage d'alias personnel. Il n'est donc pas très portable.
Jim
8

Une autre chose que les autres réponses à cette question ne semblent pas avoir directement abordée est que l'utilisation de cette méthode catn'est pas "inutile" dans le sens où "un processus de chat parasite est généré qui ne produit aucun travail"; c'est inutile en ce sens qu '"un processus de chat est créé qui ne fait que du travail inutile".

Dans le cas de ces deux:

sed 's/foo/bar/' somefile
<somefile sed 's/foo/bar/'

le shell démarre un processus sed qui lit un fichier ou un stdin (respectivement) puis effectue un traitement - il lit jusqu'à atteindre une nouvelle ligne, remplace le premier "foo" (le cas échéant) sur cette ligne par "bar", puis affiche cette ligne à stdout et boucles.

Dans le cas de:

cat somefile | sed 's/foo/bar/'

La coquille déclenche un processus chat et un processus sed, et connecte la sortie standard du chat à la commande stdin. Le processus cat lit un fichier de plusieurs kilos ou peut-être un mégaoctet dans le fichier, puis l'écrit sur sa sortie standard, où le sed sommand récupère à partir de là, comme dans le deuxième exemple ci-dessus. Pendant que sed traite ce morceau, chat lit un autre morceau et l’écrit sur sa sortie standard pour que sed puisse continuer.

En d'autres termes, le travail supplémentaire que nécessite l'ajout de la catcommande ne consiste pas uniquement à générer un catprocessus supplémentaire , mais également à lire et à écrire les octets du fichier deux fois au lieu d'une fois. Maintenant, pratiquement et sur les systèmes modernes, cela ne fait pas une différence énorme - cela peut obliger votre système à faire quelques microsecondes de travail inutile. Toutefois, s’il s’agit d’un script que vous prévoyez de distribuer, éventuellement aux personnes qui l’utilisent sur des machines déjà sous-alimentées, quelques microsecondes peuvent s’additionner au fil de nombreuses itérations.

godlygeek
la source
2
Voir oletange.blogspot.dk/2013/10/useless-use-of-cat.html pour un test de la surcharge liée à l' utilisation de l' option additionnelle cat.
Ole Tange
@OleTange: Je suis tombé sur ça et j'ai visité votre blog. (1) Alors que je vois le contenu (principalement) en anglais, je vois un tas de mots en (je suppose) danois: «Klassisk», «Flipcard», «Magasin», «Mosaik», «Sidebjælke», «Øjebliksbillede» , «Tidsskyder», «Blog-arkiv», «Om mig», «Skrevet» et «Vis kommentarer» (mais «Tweet», «J'aime» et la bannière des cookies sont en anglais). Le saviez-vous et est-il sous votre contrôle? (2) J'ai du mal à lire vos tableaux (2a) car le quadrillage est incomplet et (2b) je ne comprends pas ce que vous entendez par «Diff (pct)».
G-Man dit 'Réintégrez Monica'
blogspot.dk est géré par Google. Essayez de remplacer avec blogspot.com. Le "diff (pct)" est le ms avec catdivisé par le ms sans caten pourcentage (par exemple 264 ms / 216 ms = 1,22 = 122% = 22% plus lent avec cat)
Ole Tange