C'est probablement dans de nombreuses FAQ - au lieu d'utiliser:
cat file | command
(qui est appelé utilisation inutile de chat), manière correcte supposée être:
command < file
Dans le 2ème cas, "correct" - OS n'a pas besoin de générer un processus supplémentaire.
Malgré le fait de savoir cela, j'ai continué à utiliser un chat inutile pour 2 raisons.
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 cat
par autre chose (gzcat
, echo
, ...), d'ajouter un 2ème fichier ou d'insérer un nouveau filtre (pv
, mbuffer
, grep
...).
J'ai "senti" que cela pourrait être plus rapide dans certains cas. Plus rapide car il y a 2 processus, le 1er (cat
) fait la lecture et le second fait quoi que ce soit. 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)?
Je n'étais pas au courant de la récompense jusqu'à aujourd'hui quand une recrue a essayé de m'épingler 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é faisant référence aux origines du prix et à la pratique de le faire. Des recherches plus poussées m'ont amené à cette question. Malheureusement, malgré une réflexion consciente, aucune des réponses ne comprenait ma justification.
Je n'avais pas voulu être sur la défensive pour lui répondre. Après tout, dans ma jeunesse, j'aurais écrit la commande comme grep foo file.txt | cut ... | cut ...
parce que chaque fois que vous effectuez les simples grep
s fréquents, vous apprenez l'emplacement de l'argument de fichier et il est facile de savoir que le premier est le modèle et les derniers sont les noms de fichier.
C'était un choix conscient d'utiliser cat
lorsque j'ai répondu à la question, en partie pour une raison de "bon goût" (selon les termes de Linus Torvalds) mais principalement pour une raison de fonction convaincante.
Cette dernière raison est plus importante, je vais donc la présenter 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 très probablement le faire en silence sans message d'erreur si l'argument de fichier existe. C'est à dire. grep foo xyz | grep bar xyz | wc
vous indiquera combien de lignes dans xyz
contiennent bar
pendant que vous attendez le nombre de lignes qui contiennent à la fois foo
et bar
. Devoir changer les arguments d'une commande dans un pipeline avant de l'utiliser est sujet à des erreurs. Ajoutez-y la possibilité d'échecs silencieux et cela devient une pratique particulièrement insidieuse.
La première raison n'est pas non plus sans importance 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 juste au moment où une personne dans le besoin l'éducation dit "mais ce chat n'est-il pas inutile".
Cependant, j'essaierai également de faire prendre conscience de l'ancienne raison du "bon goût" que j'ai mentionnée. Cette raison est liée à l'esprit de conception orthogonale d'Unix. grep
ne fait pas cut
et ls
ne fait pas grep
. Par conséquent, au moins grep foo file1 file2 file3
va à l'encontre de l'esprit du design. La façon orthogonale de le faire est cat file1 file2 file3 | grep foo
. À présent, grep foo file1
n'est qu'un cas particulier de grep foo file1 file2 file3
, et si vous ne le traitez pas de la même façon, vous utilisez au moins des cycles d'horloge cérébrale en essayant d'éviter le prix inutile du chat.
Cela nous amène à l'argument selon lequel grep foo file1 file2 file3
concatène et cat
concatène donc il convient de cat file1 file2 file3
mais parce que cat
ne concatène pas dans cat file1 | grep foo
nous violons donc l'esprit à la fois du cat
et du tout-puissant Unix. Eh bien, si c'é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 crachat sur stdout). Vous auriez donc la situation où vous dites cat file1 file2
ou vous dites dog file1
et n'oubliez pas d'éviter cat file1
pour éviter d'obtenir le prix, tout en évitant dog file1 file2
puisque, espérons-le, la conception de dog
générerait une erreur si plusieurs fichiers sont spécifiés.
Avec un peu de chance, à ce stade, vous sympathisez avec les concepteurs Unix pour ne pas avoir inclus de commande séparée pour cracher un fichier sur stdout, tout en nommant cat
pour concaténer plutôt que de lui donner un autre nom. <edit>
a supprimé les commentaires incorrects sur <
, En réalité, <
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 aucun traitement supplémentaire? L'une des raisons est d'éviter d'avoir chaque commande Unix unique 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 de se rappeler: (a) où vont les arguments de nom de fichier; et (b) éviter le bogue du pipeline silencieux comme mentionné ci-dessus.
Cela nous amène à comprendre pourquoi grep
a une logique supplémentaire. La raison d'être est de permettre à l'utilisateur de maîtriser les commandes qui sont utilisées fréquemment et sur une base autonome (plutôt que comme pipeline). Il s'agit d'un léger compromis d'orthogonalité pour un gain de convivialité significatif. Toutes les commandes ne doivent pas être conçues de cette façon et les commandes qui ne sont pas fréquemment utilisées devraient éviter complètement 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
. (Soit dit en passant, notez que ls
a une raison complètement différente non seulement d'accepter mais d'exiger des arguments de fichier)
Enfin, ce qui aurait pu être mieux fait, c'est que des commandes exceptionnelles telles que grep
(mais pas nécessairement ls
) génèrent une erreur si l'entrée standard est également disponible lorsque des arguments de fichier sont spécifiés.
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, ça va:
< somefile command
est le même que
command < somefile
Deuxièmement, il y a n + 1 processus et un sous-shell se produit lorsque vous utilisez un tuyau. Il est décidément plus lent. Dans certains cas, n aurait été égal à zéro (par exemple, lorsque vous redirigez vers un shell intégré), donc en utilisant cat
vous ajoutez un nouveau processus de manière totalement inutile.
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.
Avec la version UUoC, cat
doit lire le fichier en mémoire, puis l'écrire dans le canal, et la commande doit lire les données du canal, donc le noyau doit copier le fichier entier - trois fois alors que dans le cas redirigé, le noyau n'a à copier le fichier qu'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
. Elle 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 commande tr
: c'est un filtre pur qui ignore ou rejette les arguments de nom de fichier. Pour y alimenter plusieurs fichiers, vous devez utiliser cat
comme indiqué. (Bien sûr, il y a une discussion distincte selon laquelle la conception de tr
n'est pas très bonne; il n'y a aucune raison réelle pour laquelle elle n'aurait pas pu être conçue comme un filtre standard.) Cela peut également être valide si vous souhaitez que la commande traiter toutes les entrées comme un seul fichier plutôt que comme plusieurs fichiers séparés, même si la commande accepte plusieurs fichiers distincts: par exemple, wc
est une telle commande.
C'est le cat single-file
cas qui est inconditionnellement inutile.
Je ne suis pas d'accord avec la plupart des cas de prix UUOC excessivement suffisant parce que, lorsque vous enseignez à quelqu'un d'autre, cat
est un espace de rangement pratique pour toute commande ou pipeline de commandes compliqué et croustillant qui produit une sortie adaptée au problème ou à la tâche en cours de discussion .
Cela est particulièrement vrai sur des sites comme Stack Overflow, ServerFault, Unix et Linux ou l'un des sites SE.
Si quelqu'un vous pose une question spécifique sur l'optimisation, ou si vous avez envie d'ajouter des informations supplémentaires à ce sujet, alors, parlez de la façon dont l'utilisation de cat 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 me regarder comme je suis cool! complexité.
En 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 aux gens. En réalité, ils démontrent qu'ils ne sont probablement qu'un autre débutant qui a trouvé un minuscule bâton pour battre leurs pairs.
Mise à 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 une UUOC car il est facilement possible de faire $filter
par défaut à la chaîne vide et avoir l'instruction if
do filter='| grep -v "^$"'
mais IMO, en n'incorporant pas le caractère pipe dans $filter
, cette "inutile" cat
sert le but extrêmement utile de l'auto-documentation du fait que $filter
sur la ligne printf
n'est pas seulement un autre argument de sqlplus
, c'est un filtre de sortie facultatif sélectionnable par l'utilisateur.
S'il est nécessaire d'avoir plusieurs filtres de sortie facultatifs, le traitement des options pourrait simplement ajouter | whatever
à $filter
aussi souvent que nécessaire - un cat
supplémentaire dans le pipeline ne nuira à rien ni n'entraînera de perte notable de performances.
Dans 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 ne vous en souciez pas.
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, où il n'y a rien. Plus déroutant et ressemblant à une citation de fantaisie 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 votre progression par étapes, tout en
< 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 comme programmeurs, sont beaucoup plus souvent exposés à ces derniers en tant que tels.
Et comparer deux opérandes avec <et> est contre-commutatif, ce qui signifie
(a > b) == (b < a)
Je me souviens de la première fois que j'ai utilisé <pour la redirection d'entrée, je craignais
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.
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 également être utilisé ici, au lieu d'un paramètre de fichier:
< journal.txt wc -c
15666
wc -c < journal.txt
15666
mais peu importe - 15k?
Si je rencontrais occasionnellement 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 chat n'est pas un problème. Pour la plupart des questions, l'utilisation du chat est orthogonale, hors sujet, pas un problème.
Commencer ces utilisations inutiles et inutiles des discussions sur tous les sujets sur Shell est ennuyeux et ennuyeux. Obtenez une vie et attendez votre minute de gloire, lorsque vous traitez des questions de performance.
Un problème supplémentaire est que le tuyau peut masquer silencieusement une sous-coque. Pour cet exemple, je remplacerai cat
par echo
, mais le même problème existe.
echo "foo" | while read line; do
x=$line
done
echo "$x"
Vous pouvez vous attendre à ce que x
contienne foo
, mais ce n'est pas le cas. Le x
que vous avez défini était dans un sous-shell généré pour exécuter la boucle while
. x
dans le shell qui a démarré le pipeline a une valeur indépendante ou n'est pas du tout définie.
Dans bash4, vous pouvez configurer certaines options du shell de sorte 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 x
est de nouveau local au sous-shell de while
.
En tant que personne qui souligne régulièrement cela et un certain nombre d'autres antipatterns de programmation Shell, je me sens obligé, tardivement, de peser.
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 qu'ils connaissent un peu.
Dans ce contexte, je considère qu'il est perturbateur et potentiellement destructeur de propager divers anti-modèles de script Shell. Le code que quelqu'un trouve sur Stack Overflow devrait idéalement être possible de copier/coller dans son environnement avec un minimum de changements et une compréhension incomplète.
Parmi les nombreuses ressources de script Shell sur le net, Stack Overflow est inhabituel dans la mesure où les utilisateurs peuvent aider à façonner la qualité du site en modifiant les questions et réponses sur le site. Cependant, les 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 au code.
L'UUCA et les commentaires anti-modèles associés ne sont pas uniquement destinés aux auteurs du code sur lequel nous commentons; ils sont autant un caveat emptor pour aider les lecteurs du site à devenir conscients 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 des cat
s inutiles (ou des variables non cotées, ou chmod 777
, ou une grande variété d'autres fléaux anti-modèles), mais nous pouvons au moins aider à éduquer l'utilisateur qui est sur le point de copier/coller ce code dans la boucle la plus étroite de leur script qui s'exécute des millions de fois.
En ce qui concerne les raisons techniques, la sagesse traditionnelle veut que nous essayions de minimiser le nombre de processus externes; cela continue à être un bon guide général lors de l'écriture de scripts Shell.
J'utilise souvent cat file | myprogram
dans les 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 vous attendez à une commande suivie d'arguments suivis d'une redirection. Il est possible de mettre la redirection n'importe où mais cela est rarement vu - 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 au début, vous confondez les gens qui ne sont pas habitués à cette syntaxe:
< foo program1 -o option -b option | program2
et les exemples doivent être faciles à comprendre.
C'est facile à changer.
Si vous savez que le programme peut lire à partir de cat
, vous pouvez normalement supposer qu'il peut lire la sortie de n'importe quel programme qui sort vers 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 program1 < foo
fonctionne alors cat foo | program1
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 canal, 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>'
Il y a un coût à faire le cat
supplémentaire. Pour donner une idée de combien j'ai exécuté quelques tests pour simuler la ligne de base (cat
), un faible débit (bzip2
), débit moyen (gzip
) et 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é effectué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. L'ISO $ était ubuntu-11.04-desktop-i386.iso. (Des tableaux plus jolis 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%. C'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 existe une nette différence entre les deux.
Cela conduit à la conclusion: vous devez utiliser <
au lieu de cat |
si:
Sinon, peu importe que vous utilisiez <
ou cat |
.
Et donc vous ne devriez attribuer un prix UUoC que si et seulement si: