Utilisation inutile de chat?

101

C'est probablement dans de nombreuses FAQ - au lieu d'utiliser:

cat file | command

(qui s'appelle l'utilisation inutile de chat), manière correcte supposée être:

command < file

Dans la deuxième manière, «correcte» - OS n'a pas à engendrer un processus supplémentaire.
Bien que sachant cela, j'ai continué à utiliser un chat inutile pour 2 raisons.

  1. plus esthétique - j'aime quand les données ne se déplacent uniformément que de gauche à droite. Et il est plus facile de remplacer catpar autre chose ( gzcat, echo...), ajoutez un 2ème fichier ou insérer un nouveau filtre ( pv, mbuffer, grep...).

  2. J'ai «senti» que cela pourrait être plus rapide dans certains cas. Plus rapide car il y a 2 processus, 1st ( cat) fait la lecture et le second fait tout. Et ils peuvent fonctionner en parallèle, ce qui signifie une exécution parfois plus rapide.

Ma logique est-elle correcte (pour la 2ème raison)?

Léonid Volnitsky
la source
22
catest un canal d'identité . Il diffuse uniquement son entrée vers sa sortie. Si le deuxième programme de la chaîne peut prendre son entrée du même argument que vous passez à cat(ou de l'entrée standard, si vous ne passez aucun argument), alors catest absolument inutile et ne fait qu'un processus supplémentaire étant forké et un tube supplémentaire étant établi.
Frédéric Hamidi
11
@ FrédéricHamidi quand cat n'a pas d'argument ou que son argument l'est -, c'est un tuyau d'identité. Quand il a plus d'un argument de nom de fichier sans tiret, il devient quelque chose de plus qu'un canal d'identité, cependant, et commence à servir un objectif réel.
kojiro
3
Le lien autrefois populaire vers partmaps.org est malheureusement mort. Le contenu est maintenant sur porkmail.org/era/unix/award.html
tripleee
1
Voir aussi: unix.stackexchange.com/q/511827/20336
Mikko Rantalainen
2
J'observe que si vous voulez afficher le flux de données vers la droite (raison 1), vous pouvez le faire en plaçant la redirection de fichier avant la commande, comme dans <file command1 | command2, bien qu'il y ait un désaccord sur l'esthétique.
holdenweb

Réponses:

81

Je n'étais pas au courant de la récompense jusqu'à aujourd'hui, quand une recrue a essayé de m'épingler l' UUOC pour l'une de mes réponses. C'était un cat file.txt | grep foo | cut ... | cut .... Je lui ai donné une idée de mon esprit et ce n'est qu'après avoir visité le lien qu'il m'a donné en me référant aux origines du prix et à la pratique de le faire. Des recherches plus poussées m'ont conduit à cette question. Un peu malheureusement malgré un examen conscient, aucune des réponses ne comprenait ma justification.

Je n'avais pas voulu être sur la défensive en lui répondant. Après tout, dans mes jeunes années, j'aurais écrit la commande comme grep foo file.txt | cut ... | cut ...parce que chaque fois que vous faites les simples simples, grepvous apprenez le placement de l'argument de fichier et il est prêt à savoir que le premier est le modèle et les derniers sont les noms de fichiers.

C'était un choix conscient à utiliser catlorsque j'ai répondu à la question, en partie pour une raison de «bon goût» (selon les mots de Linus Torvalds) mais principalement pour une raison impérieuse de fonction.

Cette dernière raison est plus importante, je vais donc l'exposer en premier. Lorsque j'offre un pipeline comme solution, je m'attends à ce qu'il soit réutilisable. Il est fort probable qu'un pipeline soit ajouté à la fin ou épissé dans un autre pipeline. Dans ce cas, avoir un argument de fichier pour grep gâche la réutilisabilité, et peut-être le faire silencieusement sans message d'erreur si l'argument de fichier existe. C'est à dire. grep foo xyz | grep bar xyz | wcvous indiquera le nombre de lignes xyzcontenues barpendant que vous attendez le nombre de lignes contenant à la fois fooetbar . Le fait de devoir modifier les arguments d'une commande dans un pipeline avant de l'utiliser est sujet aux erreurs. Ajoutez à cela la possibilité d'échecs silencieux et cela devient une pratique particulièrement insidieuse.

La première raison n'est pas sans importance non plus car beaucoup de " bon goût " est simplement une justification subconsciente intuitive pour des choses comme les échecs silencieux ci-dessus auxquels vous ne pouvez pas penser correctement au moment où une personne ayant besoin d'éducation dit "mais n'est pas ce chat inutile ".

Cependant, j'essaierai également de rendre conscient l'ancienne raison de «bon goût» que j'ai mentionnée. Cette raison a à voir avec l'esprit de conception orthogonale d'Unix. grepne le fait pas cutet lsne le fait pas grep. Par conséquent, à tout le moins grep foo file1 file2 file3va à l'encontre de l'esprit du design. La manière orthogonale de le faire est cat file1 file2 file3 | grep foo. Maintenant, grep foo file1c'est simplement un cas particulier de grep foo file1 file2 file3, et si vous ne le traitez pas de la même manière, vous utilisez au moins des cycles d'horloge cérébrale en essayant d'éviter le prix du chat inutile.

Cela nous amène à l'argument qui grep foo file1 file2 file3concatène et catconcatène donc c'est propre, cat file1 file2 file3mais parce que ce catn'est pas concaténant, cat file1 | grep foonous violons donc l'esprit à la fois catdu tout-puissant Unix. Eh bien, si tel était le cas, Unix aurait besoin d'une commande différente pour lire la sortie d'un fichier et le cracher sur stdout (pas le paginer ou quoi que ce soit juste un pur crachement vers stdout). Ainsi, vous auriez la situation où vous dites cat file1 file2ou vous dites dog file1et rappelez-vous consciencieusement d'éviter cat file1d'éviter d'obtenir le prix, tout en évitant également dog file1 file2car, espérons-le, la conception de doglèverait une erreur si plusieurs fichiers sont spécifiés.

Espérons qu'à ce stade, vous sympathisez avec les concepteurs Unix pour ne pas inclure une commande séparée pour cracher un fichier sur stdout, tout en nommant catpour concaténer plutôt que de lui donner un autre nom. <edit>supprimé les commentaires incorrects sur <, en fait, <est une fonction efficace sans copie pour cracher un fichier sur stdout que vous pouvez positionner au début d'un pipeline afin que les concepteurs Unix aient inclus quelque chose spécifiquement pour cela</edit>

La question suivante est pourquoi est-il important d'avoir des commandes qui crachent simplement un fichier ou la concaténation de plusieurs fichiers vers stdout, sans autre traitement? Une des raisons est d'éviter d'avoir chaque commande Unix qui fonctionne sur une entrée standard pour savoir comment analyser au moins un argument de fichier de ligne de commande et l'utiliser comme entrée s'il existe. La deuxième raison est d'éviter aux utilisateurs d'avoir à se souvenir: (a) où vont les arguments du nom de fichier; et (b) éviter le bogue du pipeline silencieux comme mentionné ci-dessus.

Cela nous amène à pourquoi grepa la logique supplémentaire. La justification est de permettre à l'utilisateur de maîtriser les commandes fréquemment utilisées et de manière autonome (plutôt que sous forme de pipeline). C'est un léger compromis d'orthogonalité pour un gain significatif en ergonomie. Toutes les commandes ne doivent pas être conçues de cette façon et les commandes qui ne sont pas fréquemment utilisées doivent complètement éviter la logique supplémentaire des arguments de fichier (rappelez-vous que la logique supplémentaire conduit à une fragilité inutile (la possibilité d'un bogue)). L'exception est d'autoriser les arguments de fichier comme dans le cas de grep. (Au fait, notez que cela lsa une raison complètement différente non seulement d'accepter mais d'exiger à peu près des arguments de fichier)

Enfin, ce qui aurait pu être mieux fait, c'est si des commandes exceptionnelles comme grep(mais pas nécessairement ls) génèrent une erreur si l'entrée standard est également disponible lorsque les arguments de fichier sont spécifiés.

nécromancien
la source
53
Notez que lorsqu'il grepest appelé avec plusieurs noms de fichier, il préfixe les lignes trouvées avec le nom du fichier dans lequel il a été trouvé (sauf si vous désactivez ce comportement). Il peut également signaler les numéros de ligne dans les fichiers individuels. Si vous ne l'utilisez que catpour alimenter grep, vous perdez les noms de fichiers et les numéros de ligne sont continus sur tous les fichiers, pas par fichier. Ainsi, il y a des raisons d'avoir grepgérer plusieurs fichiers lui-même qui catne peuvent pas gérer. Les cas de fichier unique et zéro fichier sont simplement des cas particuliers d'utilisation générale de fichiers multiples grep.
Jonathan Leffler
38
Comme indiqué dans la réponse de kojiro , il est parfaitement possible et légal de démarrer le pipeline < file command1 .... Bien que la position conventionnelle des opérateurs de redirection d'E / S soit après le nom de la commande et ses arguments, ce n'est que la convention et non un placement obligatoire. Le <doit précéder le nom du fichier. Donc, il y a une symétrie parfaite près de entre >outputet <inputRedirections: <input command1 -opt 1 | command2 -o | command3 >output.
Jonathan Leffler
15
Je pense que l'une des raisons pour lesquelles les gens jettent la pierre UUoC (moi y compris) est principalement d'éduquer. Parfois, les gens traitent d'énormes fichiers texte de gigaoctets, auquel cas la minimisation des tuyaux (UUoC, regroupement des greps séquentiels en un, etc.) est cruciale et souvent on peut supposer en toute sécurité en se basant sur la question que l'OP ne sait tout simplement pas que de petits ajustements pourraient avoir d'énormes impacts sur les performances. Je suis entièrement d'accord avec votre point de vue sur les cycles cérébraux et c'est pourquoi je me retrouve à utiliser le chat régulièrement, même lorsque cela n'est pas nécessaire. Mais il est important de savoir que ce n'est pas nécessaire.
Adrian Frühwirth
13
Essaye de comprendre; Je ne dis en aucun cas que cela catne sert à rien. Ce n'est pas cela qui ne catsert à rien; c'est qu'une construction particulière n'a pas besoin d'être utilisée cat. Si vous le souhaitez, notez que c'est UUoC (Useless Use of cat), et non UoUC (Use of Useless cat). Il existe de nombreuses occasions où catest le bon outil à utiliser; Je n'ai aucun problème avec son utilisation quand c'est le bon outil à utiliser (et, en fait, je mentionne un cas dans ma réponse).
Jonathan Leffler
6
@randomstring je vous entends, mais je pense que cela dépend vraiment du cas d'utilisation. Lorsqu'il est utilisé sur la ligne de commande, un élément supplémentaire catdans le tube peut ne pas être un gros problème en fonction des données, mais lorsqu'il est utilisé comme environnement de programmation, il peut être absolument nécessaire d'implémenter ces éléments critiques pour les performances; surtout lorsqu'il s'agit de savoir bashqui, en termes de performances, est comme une roue de forme rectangulaire (par rapport à de kshtoute façon. Je parle jusqu'à 10 fois plus lentement ici - sans blague). Vous ne souhaitez optimiser vos fourches (et pas seulement) lorsqu'ils traitent avec de plus grands scripts ou des boucles énormes.
Adrian Frühwirth
58

Nan!

Tout d'abord, peu importe où dans une commande la redirection se produit. Donc, si vous aimez votre redirection vers la gauche de votre commande, c'est très bien:

< somefile command

est le même que

command < somefile

Deuxièmement, il y a n + 1 processus et un sous-shell qui se produisent lorsque vous utilisez un tube. C'est décidément plus lent. Dans certains cas, n aurait été zéro (par exemple, lorsque vous redirigez vers un shell intégré), donc en utilisant catvous ajoutez un nouveau processus totalement inutilement.

En général, chaque fois que vous vous retrouvez à utiliser un tuyau, cela vaut la peine de prendre 30 secondes pour voir si vous pouvez l'éliminer. (Mais cela ne vaut probablement pas la peine de prendre plus de 30 secondes.) Voici quelques exemples où les tuyaux et les processus sont fréquemment utilisés inutilement:

for word in $(cat somefile);  # for word in $(<somefile); … (or better yet, while read < somefile)

grep something | awk stuff; # awk '/something/ stuff' (similar for sed)

echo something | command; # command <<< something (although echo would be necessary for pure POSIX)

N'hésitez pas à modifier pour ajouter plus d'exemples.

Kojiro
la source
2
Eh bien, l'augmentation de la vitesse ne sera pas beaucoup.
Dakkaron
9
placer le "<somefile" avant "command" vous donne techniquement de gauche à droite, mais cela rend la lecture ambiguë car il n'y a pas de démarcation syntaxique: < cat grep dogest un exemple artificiel pour montrer que vous ne pouvez pas facilement distinguer le fichier d'entrée, la commande qui reçoit l'entrée et les arguments de la commande.
nécromancien
2
La règle de base que j'ai adoptée pour décider où va la redirection STDIN est de faire tout ce qui minimise l' apparence d'ambiguïté / potentiel de surprise. Dire dogmatiquement qu'il va avant soulève le problème du nécromancien, mais dire dogmatiquement qu'il va après peut faire la même chose. Considérez: stdout=$(foo bar -exec baz <qux | ENV=VAR quux). Q. S'applique-t-il <quxà foo, ou à baz, qui est -exec'd by foo? R. Cela s'applique à foo, mais peut paraître ambigu. Mettre <qux avant foo dans ce cas est plus clair, bien que moins courant, et est analogue à la fin ENV=VAR quux.
Mark G.
3
@necromancer, <"cat" grep dogest plus facile à lire, là-bas. (Je suis généralement pro-espace, mais ce cas particulier est vraiment une exception).
Charles Duffy
1
@kojiro "C'est décidément plus lent." Vous ne pouvez pas écrire cela sans étayer cela avec des chiffres. Mes numéros sont ici: oletange.blogspot.com/2013/10/useless-use-of-cat.html (et ils montrent que c'est seulement plus lent lorsque vous avez un débit élevé) Où sont les vôtres?
Ole Tange
30

Je ne suis pas d'accord avec la plupart des exemples du prix UUOC excessivement suffisant car, lorsque vous enseignez à quelqu'un d'autre, il cats'agit d'un espace réservé pratique pour toute commande ou pipeline compliqué et croustillant de commandes qui produisent une sortie adaptée au problème ou à la tâche discutée.

Cela est particulièrement vrai sur des sites tels que Stack Overflow, ServerFault, Unix et Linux ou l'un des sites SE.

Si quelqu'un pose spécifiquement des questions sur l'optimisation ou si vous avez envie d'ajouter des informations supplémentaires à ce sujet, alors, bien, expliquez à quel point l'utilisation de chat est inefficace. Mais ne réprimandez pas les gens parce qu'ils ont choisi de viser la simplicité et la facilité de compréhension dans leurs exemples plutôt que de regarder-moi-comment-cool-suis-je! complexité.

Bref, parce que le chat n'est pas toujours un chat.

Aussi parce que la plupart des gens qui aiment décerner des UUOC le font parce qu'ils sont plus soucieux de montrer à quel point ils sont `` intelligents '' que d'aider ou d'enseigner les gens. En réalité, ils démontrent qu'ils sont probablement juste un autre débutant qui a trouvé un petit bâton avec lequel battre leurs pairs.


Mettre à jour

Voici un autre UUOC que j'ai publié dans une réponse à https://unix.stackexchange.com/a/301194/7696 :

sqlq() {
  local filter
  filter='cat'

  # very primitive, use getopts for real option handling.
  if [ "$1" == "--delete-blank-lines" ] ; then
    filter='grep -v "^$"'
    shift
  fi

  # each arg is piped into sqlplus as a separate command
  printf "%s\n" "$@" | sqlplus -S sss/eee@sid | $filter
}

Les pédants UUOC diraient que c'est un UUOC parce qu'il est facilement possible de faire $filterpar défaut la chaîne vide et d'avoir l' ifinstruction do filter='| grep -v "^$"'mais IMO, en n'incorporant pas le caractère pipe dans $filter, ce "inutile" catsert le but extrêmement utile d'auto-documenter le fait qui $filtersur la printfligne n'est pas simplement un autre argument sqlplus, c'est un filtre de sortie optionnel sélectionnable par l'utilisateur.

S'il y a besoin d'avoir plusieurs filtres de sortie en option, le traitement des options pourrait tout append | whateverà $filteraussi souvent que nécessaire - un supplémentaire catdans le pipeline ne va pas quoi que ce soit blessé ou causer une perte notable de performance.

cas
la source
11
En passant, l' ==intérieur [ ]n'est pas spécifié par POSIX, et toutes les implémentations ne l'acceptent pas. L'opérateur standardisé est juste =.
Charles Duffy
27

Avec la version UUoC, catil faut lire le fichier en mémoire, puis l'écrire dans le tube, et la commande doit lire les données du tube, le noyau doit donc copier le fichier entier trois fois alors que dans le cas de la redirection, le noyau n'a qu'à copier le fichier une seule fois. Il est plus rapide de faire quelque chose une fois que de le faire trois fois.

En utilisant:

cat "$@" | command

est une utilisation totalement différente et pas nécessairement inutile de cat. Il est toujours inutile si la commande est un filtre standard qui accepte zéro ou plusieurs arguments de nom de fichier et les traite à son tour. Considérez la trcommande: c'est un filtre pur qui ignore ou rejette les arguments de nom de fichier. Pour y alimenter plusieurs fichiers, vous devez utiliser catcomme indiqué. (Bien sûr, il y a une discussion distincte sur le fait que la conception trn'est pas très bonne; il n'y a aucune vraie raison pour laquelle elle n'aurait pas pu être conçue comme un filtre standard.) Cela peut également être valide si vous voulez que la commande traite toutes les entrées comme un un seul fichier plutôt que plusieurs fichiers séparés, même si la commande accepterait plusieurs fichiers séparés: par exemple, wcest une telle commande.

C'est le cat single-filecas qui est inconditionnellement inutile.

Jonathan Leffler
la source
26

Pour la défense du chat:

Oui,

   < input process > output 

ou

   process < input > output 

est plus efficace, mais de nombreuses invocations n'ont pas de problèmes de performances, donc vous vous en fichez.

raisons ergonomiques:

Nous avons l'habitude de lire de gauche à droite, donc une commande comme

    cat infile | process1 | process2 > outfile

est trivial à comprendre.

    process1 < infile | process2 > outfile

doit sauter par-dessus process1, puis lire de gauche à droite. Cela peut être guéri par:

    < infile process1 | process2 > outfile

ressemble en quelque sorte, comme s'il y avait une flèche pointant vers la gauche, là où rien n'est. Plus déroutant et ressemblant à des citations sophistiquées, c'est:

    process1 > outfile < infile

et la génération de scripts est souvent un processus itératif,

    cat file 
    cat file | process1
    cat file | process1 | process2 
    cat file | process1 | process2 > outfile

où vous voyez vos progrès par étapes, tandis que

    < file 

ne fonctionne même pas. Les méthodes simples sont moins sujettes aux erreurs et la caténation des commandes ergonomiques est simple avec cat.

Un autre sujet est que la plupart des gens ont été exposés à> et <en tant qu'opérateurs de comparaison, bien avant d'utiliser un ordinateur et lorsqu'ils utilisent un ordinateur en tant que programmeurs, y sont beaucoup plus souvent exposés en tant que tels.

Et comparer deux opérandes avec <et> est contra-commutatif, ce qui signifie

(a > b) == (b < a)

Je me souviens de la première fois que j'utilisais <pour la redirection d'entrée, j'avais peur

a.sh < file 

pourrait signifier la même chose que

file > a.sh

et en quelque sorte écraser mon script a.sh. C'est peut-être un problème pour de nombreux débutants.

rares différences

wc -c journal.txt
15666 journal.txt
cat journal.txt | wc -c 
15666

Ce dernier peut être utilisé directement dans les calculs.

factor $(cat journal.txt | wc -c)

Bien sûr, le <peut être utilisé ici aussi, au lieu d'un paramètre de fichier:

< journal.txt wc -c 
15666
wc -c < journal.txt
15666
    

mais qui s'en soucie - 15k?

Si je rencontrais parfois des problèmes, je changerais sûrement mon habitude d'invoquer le chat.

Lorsque vous utilisez des fichiers très volumineux ou très nombreux, éviter cat est très bien. Pour la plupart des questions, l'utilisation du chat est orthogonale, hors sujet, pas un problème.

Commencer ces utilisations inutiles et inutiles de la discussion de chat sur un sujet sur deux n'est que ennuyeux et ennuyeux. Obtenez une vie et attendez votre minute de gloire, lorsque vous traitez des questions de performance.

Utilisateur inconnu
la source
5
+11111 .. En tant qu'auteur de la réponse actuellement acceptée, je recommande vivement ce délicieux complément. Les exemples spécifiques éclairent mes arguments souvent abstraits et verbeux, et le rire que vous obtenez de l'inquiétude précoce de l'auteur file > a.shvaut à lui seul le temps de lire ceci :) Merci pour le partage!
nécromancien
Dans cet appel cat file | wc -c, wcdoit lire stdin jusqu'à EOF, en comptant les octets. Mais dans ce cas, wc -c < fileil ne fait que stats stdin, découvre que c'est un fichier normal et affiche st_size au lieu de lire une entrée. Pour un fichier volumineux, la différence de performances serait clairement visible.
oguz ismail
18

Un problème supplémentaire est que le tuyau peut masquer silencieusement un sous-coque. Pour cet exemple, je vais remplacer catpar echo, mais le même problème existe.

echo "foo" | while read line; do
    x=$line
done

echo "$x"

Vous pourriez vous attendre xà contenir foo, mais ce n'est pas le cas. Le que xvous avez défini était dans un sous-shell généré pour exécuter la whileboucle. xdans le shell qui a démarré le pipeline a une valeur sans rapport ou n'est pas du tout définie.

Dans bash4, vous pouvez configurer certaines options du shell afin que la dernière commande d'un pipeline s'exécute dans le même shell que celui qui démarre le pipeline, mais vous pouvez alors essayer ceci

echo "foo" | while read line; do
    x=$line
done | awk '...'

et xest une fois de plus locale au whilesous-shell de.

chepner
la source
5
Dans les shells strictement POSIX, cela peut être un problème délicat car vous n'avez pas ici de chaînes ou de substitutions de processus pour éviter le tube. BashFAQ 24 a des solutions utiles même dans ce cas.
kojiro le
4
Dans certains shells, le tube illustré ne crée pas de sous-coque. Les exemples incluent Korn et Z. Ils prennent également en charge la substitution de processus et ici les chaînes. Bien sûr, ils ne sont pas strictement POSIX. Bash 4 doit shopt -s lastpipeéviter de créer le sous-shell.
Suspendu jusqu'à nouvel ordre.
13

En tant que personne qui le souligne régulièrement et un certain nombre d'autres anti-modèles de programmation shell, je me sens obligé de peser tardivement.

Le script Shell est un langage de copier / coller. Pour la plupart des gens qui écrivent des scripts shell, ils ne sont pas là pour apprendre la langue; c'est juste un obstacle qu'ils doivent surmonter pour continuer à faire les choses dans la ou les langues avec lesquelles ils sont réellement familiers.

Dans ce contexte, je considère qu'il est perturbateur et même potentiellement destructeur de propager divers anti-modèles de script shell. Le code que quelqu'un trouve sur Stack Overflow devrait idéalement pouvoir être copié / collé dans son environnement avec des modifications minimes et une compréhension incomplète.

Parmi les nombreuses ressources de script shell sur le net, Stack Overflow est inhabituel en ce que les utilisateurs peuvent aider à façonner la qualité du site en éditant les questions et réponses sur le site. cependant, modifications de code peuvent être problématiques car il est facile d'apporter des modifications qui n'étaient pas prévues par l'auteur du code. Par conséquent, nous avons tendance à laisser des commentaires pour suggérer des modifications du code.

L'UUCA et les commentaires antipattern associés ne sont pas réservés uniquement aux auteurs du code que nous commentons; ils sont autant une mise en garde pour aider les lecteurs du site à prendre conscience des problèmes dans le code qu'ils trouvent ici.

Nous ne pouvons pas espérer parvenir à une situation où aucune réponse sur Stack Overflow ne recommande inutile cat s (ou des variables non citées, ou chmod 777, ou une grande variété d'autres fléaux antipattern), mais nous pouvons au moins aider à éduquer l'utilisateur qui est sur le point de copier / collez ce code dans la boucle étroite la plus interne de leur script qui s'exécute des millions de fois.

En ce qui concerne les raisons techniques, la sagesse traditionnelle est que nous devrions essayer de minimiser le nombre de processus externes; cela continue à être une bonne indication générale lors de l'écriture de scripts shell.

tripleee
la source
2
De plus, pour les fichiers volumineux, le transfert catest un grand nombre de commutateurs de contexte et de bande passante mémoire supplémentaires (et la pollution du cache L3 à partir de copies supplémentaires de données dans catle tampon de lecture de et les tampons de canal). Surtout sur une grosse machine multicœur (comme de nombreuses configurations d'hébergement), la bande passante cache / mémoire est une ressource partagée.
Peter Cordes
1
@PeterCordes Veuillez poster vos mesures. Nous pouvons donc si cela compte vraiment dans la pratique. Mon expérience est que cela n'a normalement pas d'importance: oletange.blogspot.com/2013/10/useless-use-of-cat.html
Ole Tange
1
Votre propre blog montre un ralentissement de 50% pour le haut débit, et vous ne regardez même pas l'impact sur le débit total (si vous aviez des choses qui occupaient les autres cœurs). Si j'y arrive, je pourrais exécuter vos tests pendant que x264 ou x265 encodent une vidéo en utilisant tous les cœurs, et voir à quel point cela ralentit l'encodage vidéo. bzip2et la gzipcompression sont toutes deux très lentes par rapport à la quantité de frais généraux qui cats'ajoute à cela seul (avec la machine autrement inactive). Il est difficile de lire vos tableaux (retour à la ligne au milieu d'un nombre?). sysle temps augmente beaucoup, mais reste petit par rapport à l'utilisateur ou réel?
Peter Cordes
8

J'utilise souvent cat file | myprogramdans des exemples. Parfois, je suis accusé d'utilisation inutile de chat ( http://porkmail.org/era/unix/award.html ). Je ne suis pas d'accord pour les raisons suivantes:

  • Il est facile de comprendre ce qui se passe.

    Lors de la lecture d'une commande UNIX, vous attendez une commande suivie d'arguments suivie d'une redirection. Il est possible de placer la redirection n'importe où, mais elle est rarement vue - ainsi les gens auront plus de mal à lire l'exemple. Je crois

    cat foo | program1 -o option -b option | program2

    est plus facile à lire que

    program1 -o option -b option < foo | program2

    Si vous déplacez la redirection vers le début, vous confondez les personnes qui ne sont pas habituées à cette syntaxe:

    < foo program1 -o option -b option | program2

    et les exemples doivent être faciles à comprendre.

  • Il est facile de changer.

    Si vous savez que le programme peut lire cat , vous pouvez normalement supposer qu'il peut lire la sortie de tout programme qui sort sur STDOUT, et ainsi vous pouvez l'adapter à vos propres besoins et obtenir des résultats prévisibles.

  • Il souligne que le programme n'échoue pas, si STDIN n'est pas un fichier.

    Il n'est pas sûr de supposer que si cela program1 < foofonctionne, cat foo | program1cela fonctionnera également. Cependant, il est prudent de supposer le contraire. Ce programme fonctionne si STDIN est un fichier, mais échoue si l'entrée est un tube, car il utilise la recherche:

    # works
    < foo perl -e 'seek(STDIN,1,1) || die;print <STDIN>'
    
    # fails
    cat foo | perl -e 'seek(STDIN,1,1) || die;print <STDIN>'

Coût de la performance

Il y a un coût pour faire le supplément cat. Pour donner une idée de combien j'ai exécuté quelques tests pour simuler la ligne de base ( cat), le débit faible ( bzip2), le débit moyen ( gzip) et le débit élevé ( grep).

cat $ISO | cat
< $ISO cat
cat $ISO | bzip2
< $ISO | bzip2
cat $ISO | gzip
< $ISO gzip
cat $ISO | grep no_such_string
< $ISO grep no_such_string

Les tests ont été exécutés sur un système bas de gamme (0,6 GHz) et un ordinateur portable ordinaire (2,2 GHz). Ils ont été exécutés 10 fois sur chaque système et le meilleur timing a été choisi pour imiter la situation optimale pour chaque test. Le $ ISO était ubuntu-11.04-desktop-i386.iso. (De jolis tableaux ici: http://oletange.blogspot.com/2013/10/useless-use-of-cat.html )

CPU                       0.6 GHz ARM
Command                   cat $ISO|                        <$ISO                            Diff                             Diff (pct)
Throughput \ Time (ms)    User       Sys        Real       User       Sys        Real       User       Sys        Real       User       Sys        Real
Baseline (cat)                     55      14453      33090         23       6937      33126         32       7516        -36        239        208         99
Low (bzip2)                   1945148      16094    1973754    1941727       5664    1959982       3420      10430      13772        100        284        100
Medium (gzip)                  413914      13383     431812     407016       5477     416760       6898       7906      15052        101        244        103
High (grep no_such_string)      80656      15133      99049      79180       4336      86885       1476      10797      12164        101        349        114

CPU                       Core i7 2.2 GHz
Command                   cat $ISO|           <$ISO             Diff          Diff (pct)
Throughput \ Time (ms)    User     Sys Real   User   Sys Real   User Sys Real User       Sys Real
Baseline (cat)                    0 356    215      1  84     88    0 272  127          0 423  244
Low (bzip2)                  136184 896 136765 136728 160 137131 -545 736 -366         99 560   99
Medium (gzip)                 26564 788  26791  26332 108  26492  232 680  298        100 729  101
High (grep no_such_string)      264 392    483    216  84    304   48 308  179        122 466  158

Les résultats montrent que pour un débit faible et moyen, le coût est de l'ordre de 1%. Ceci est bien dans l'incertitude des mesures, donc en pratique il n'y a pas de différence.

Pour un débit élevé, la différence est plus grande et il y a une nette différence entre les deux.

Cela mène à la conclusion: vous devriez utiliser <au lieu de cat |if:

  • la complexité du traitement est similaire à un simple grep
  • la performance compte plus que la lisibilité.

Sinon, peu importe que vous utilisiez <ou cat |.

Et donc, vous ne devriez donner un prix UUoC que si et seulement si:

  • vous pouvez mesurer une différence significative dans la performance (publier vos mesures lorsque vous attribuez le prix)
  • la performance compte plus que la lisibilité.
Ole Tange
la source
-3

Je pense que (la manière traditionnelle) d'utiliser le tuyau est un peu plus rapide; sur ma boîte, j'ai utilisé la stracecommande pour voir ce qui se passe:

Sans tuyau:

toc@UnixServer:~$ strace wc -l < wrong_output.c
execve("/usr/bin/wc", ["wc", "-l"], [/* 18 vars */]) = 0
brk(0)                                  = 0x8b50000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77ad000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=29107, ...}) = 0
mmap2(NULL, 29107, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb77a5000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\222\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1552584, ...}) = 0
mmap2(NULL, 1563160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7627000
mmap2(0xb779f000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x178) = 0xb779f000
mmap2(0xb77a2000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb77a2000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7626000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb76268d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb779f000, 8192, PROT_READ)   = 0
mprotect(0x804f000, 4096, PROT_READ)    = 0
mprotect(0xb77ce000, 4096, PROT_READ)   = 0
munmap(0xb77a5000, 29107)               = 0
brk(0)                                  = 0x8b50000
brk(0x8b71000)                          = 0x8b71000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=5540198, ...}) = 0
mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7426000
mmap2(NULL, 1507328, PROT_READ, MAP_PRIVATE, 3, 0x2a8) = 0xb72b6000
close(3)                                = 0
open("/usr/share/locale/locale.alias", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=2570, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77ac000
read(3, "# Locale name alias data base.\n#"..., 4096) = 2570
read(3, "", 4096)                       = 0
close(3)                                = 0
munmap(0xb77ac000, 4096)                = 0
open("/usr/share/locale/fr_FR.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr_FR.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr_FR/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/fr/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr_FR.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr_FR.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr_FR/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/fr/LC_MESSAGES/coreutils.mo", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=316721, ...}) = 0
mmap2(NULL, 316721, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7268000
close(3)                                = 0
open("/usr/lib/i386-linux-gnu/gconv/gconv-modules.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=26064, ...}) = 0
mmap2(NULL, 26064, PROT_READ, MAP_SHARED, 3, 0) = 0xb7261000
close(3)                                = 0
read(0, "#include<stdio.h>\n\nint main(int "..., 16384) = 180
read(0, "", 16384)                      = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7260000
write(1, "13\n", 313
)                     = 3
close(0)                                = 0
close(1)                                = 0
munmap(0xb7260000, 4096)                = 0
close(2)                                = 0
exit_group(0)                           = ?

Et avec tuyau:

toc@UnixServer:~$ strace cat wrong_output.c | wc -l
execve("/bin/cat", ["cat", "wrong_output.c"], [/* 18 vars */]) = 0
brk(0)                                  = 0xa017000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb774b000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=29107, ...}) = 0
mmap2(NULL, 29107, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7743000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\222\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1552584, ...}) = 0
mmap2(NULL, 1563160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb75c5000
mmap2(0xb773d000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x178) = 0xb773d000
mmap2(0xb7740000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7740000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb75c4000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb75c48d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb773d000, 8192, PROT_READ)   = 0
mprotect(0x8051000, 4096, PROT_READ)    = 0
mprotect(0xb776c000, 4096, PROT_READ)   = 0
munmap(0xb7743000, 29107)               = 0
brk(0)                                  = 0xa017000
brk(0xa038000)                          = 0xa038000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=5540198, ...}) = 0
mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb73c4000
mmap2(NULL, 1507328, PROT_READ, MAP_PRIVATE, 3, 0x2a8) = 0xb7254000
close(3)                                = 0
fstat64(1, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
open("wrong_output.c", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0664, st_size=180, ...}) = 0
read(3, "#include<stdio.h>\n\nint main(int "..., 32768) = 180
write(1, "#include<stdio.h>\n\nint main(int "..., 180) = 180
read(3, "", 32768)                      = 0
close(3)                                = 0
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
13

Vous pouvez faire des tests avec straceet timecommander avec des commandes de plus en plus longues pour un bon benchmarking.

TOC
la source
9
Je ne comprends pas ce que vous entendez par (de manière traditionnelle) utiliser pipe , ou pourquoi vous pensez que cela stracemontre que c'est plus rapide - le stracene trace pas l' wc -lexécution dans le second cas. Il ne trace ici que la première commande du pipeline.
kojiro le
@kojiro: je veux dire par voie traditionnelle = la manière la plus utilisée (je pense que nous utilisons le tuyau plus que l'indirection), je ne peux pas confirmer que c'est plus rapide ou pas, dans ma trace j'ai vu plus d'appels système pour l'indirection. Vous pouvez utiliser un programme ac et une boucle pour voir avec un consommer plus de temps. Si vous êtes intéressé, nous pouvons le mettre ici :)
TOC
3
Une comparaison des pommes aux pommes serait mise à strace -f sh -c 'wc -l < wrong_output.c'côté strace -f sh -c 'cat wrong_output.c | wc -l'.
Charles Duffy
5
Voici les résultats de ideone.com, qui sont actuellement clairement en faveur du sans cat: ideone.com/2w1W42#stderr
tripleee
1
@CharlesDuffy: mkfifocrée un tube nommé . Un canal anonyme est mis en place avec pipe(2)puis forking, et le parent et l'enfant ferment différentes extrémités du tuyau. Mais oui, cette réponse est un non-sens total, et n'a même pas essayé de compter les appels système ou de l'utiliser strace -Opour mesurer la surcharge, ou -rpour horodater chaque appel par rapport au dernier ...
Peter Cordes