L'utilisation d'une boucle while pour traiter du texte est-elle généralement considérée comme une mauvaise pratique dans les shells POSIX?
Comme l'a souligné Stéphane Chazelas , certaines des raisons de ne pas utiliser shell loop sont conceptuelles , fiabilité , lisibilité , performances et sécurité .
Cette réponse explique les aspects de fiabilité et de lisibilité :
while IFS= read -r line <&3; do
printf '%s\n' "$line"
done 3< "$InputFile"
En termes de performances , la while
boucle et la lecture sont extrêmement lentes lors de la lecture d'un fichier ou d'un tube, car le shell de lecture intégré lit un caractère à la fois.
Qu'en est- il des aspects conceptuels et de sécurité ?
shell
text-processing
cuonglm
la source
la source
yes
écrire dans un fichier si rapidement?bash
, il lit une taille de tampon à la fois, essayezdash
par exemple. Voir aussi unix.stackexchange.com/q/209123/38906Réponses:
Oui, nous voyons un certain nombre de choses comme:
Ou pire:
(ne rigole pas, j'en ai vu beaucoup).
Généralement des débutants en scripts shell. Ce sont des traductions littérales naïves de ce que vous feriez dans des langages impératifs tels que C ou python, mais ce n'est pas ainsi que vous faites les choses dans les shells, et ces exemples sont très inefficaces, complètement peu fiables (pouvant potentiellement poser des problèmes de sécurité), et si vous réussissez un jour pour corriger la plupart des bugs, votre code devient illisible.
Conceptuellement
En C ou dans la plupart des autres langues, les blocs de construction ne représentent qu'un niveau au-dessus des instructions de l'ordinateur. Vous dites à votre processeur quoi faire et ensuite quoi faire. Vous prenez votre processeur par la main et vous le gérez: vous ouvrez ce fichier, vous lisez autant d'octets, vous faites ceci, vous le faites avec cela.
Les coquillages sont une langue de niveau supérieur. On peut dire que ce n'est même pas une langue. Ils sont avant tous les interprètes en ligne de commande. Le travail est effectué à l'aide des commandes que vous exécutez et le shell n'a pour but que de les orchestrer.
Un des grands avantages d'Unix est le pipe et les flux stdin / stdout / stderr par défaut que toutes les commandes gèrent par défaut.
En 45 ans, nous n'avons pas trouvé meilleur que cette API pour exploiter la puissance des commandes et les faire coopérer à une tâche. C'est probablement la raison principale pour laquelle les gens utilisent encore des coquilles aujourd'hui.
Vous avez un outil de coupe et un outil de translittération, et vous pouvez simplement faire:
Le shell se contente de faire la plomberie (ouvrir les fichiers, configurer les tuyaux, invoquer les commandes) et quand tout est prêt, il coule sans que le shell ne fasse rien. Les outils font leur travail simultanément, efficacement, à leur propre rythme, avec suffisamment de mémoire tampon pour qu’aucun ne bloque l’autre, il est tout simplement magnifique et pourtant si simple.
Invoquer un outil a cependant un coût (et nous le développerons sur le point de performance). Ces outils peuvent être écrits avec des milliers d’instructions en C. Un processus doit être créé, l’outil doit être chargé, initialisé, puis nettoyé, le processus détruit et attendu.
Invoquer,
cut
c'est comme ouvrir le tiroir de la cuisine, prendre le couteau, l'utiliser, le laver, le sécher, le remettre dans le tiroir. Quand tu fais:C'est comme pour chaque ligne du fichier, extraire l'
read
outil du tiroir de la cuisine (très maladroit parce qu'il n'a pas été conçu pour cela ), lire une ligne, laver votre outil de lecture, le remettre dans le tiroir. Planifiez ensuite une réunion pour l' outilecho
etcut
, sortez-les du tiroir, appelez-les, nettoyez-les, séchez-les, remettez-les dans le tiroir, etc.Certains de ces outils (
read
etecho
) sont construits dans la plupart des coques, mais cela ne fait guère de différence iciecho
etcut
doit encore être exécuté dans des processus séparés.C'est comme couper un oignon mais laver son couteau et le remettre dans le tiroir de la cuisine entre chaque tranche.
Ici, le moyen le plus évident consiste à sortir votre
cut
outil du tiroir, à trancher tout votre oignon et à le remettre dans le tiroir une fois le travail terminé.IOW, dans les shells, en particulier pour traiter du texte, vous appelez le moins d’utilitaires possible et les faites coopérer à la tâche. Vous n’exécutez pas des milliers d’outils en ordre en attendant que chacun d’entre eux démarre, nettoient avant d’exécuter le suivant.
Pour en savoir plus, lisez bien Bruce . Les outils internes de traitement de texte de bas niveau dans les shells (à l'exception peut-être de
zsh
) sont limités, encombrants et ne conviennent généralement pas au traitement de texte général.Performance
Comme indiqué précédemment, l'exécution d'une commande a un coût. Un coût énorme si cette commande n'est pas intégrée, mais même si elles sont intégrées, le coût est élevé.
Et les shells n’ont pas été conçus pour fonctionner comme ça, ils ne prétendent pas être des langages de programmation performants. Ils ne sont pas, ils sont juste des interprètes en ligne de commande. Donc, peu d'optimisation a été faite sur ce front.
En outre, les shells exécutent des commandes dans des processus distincts. Ces blocs de construction ne partagent pas une mémoire ou un état commun. Quand vous faites un
fgets()
oufputs()
en C, c'est une fonction dans stdio. stdio conserve les mémoires tampons internes d'entrée et de sortie pour toutes les fonctions stdio, afin d'éviter de faire des appels système coûteux trop souvent.Les utilitaires shell même BUILTIN correspondant (
read
,echo
,printf
) ne peuvent pas le faire.read
est destiné à lire une ligne. S'il lit après le caractère de nouvelle ligne, cela signifie que la prochaine commande que vous exécuterez le manquera. Ilread
faut donc lire l’entrée un octet à la fois (certaines implémentations ont une optimisation si l’entrée est un fichier normal dans la mesure où elles lisent des morceaux et recherchent en arrière, mais cela ne fonctionne que pour des fichiers normaux etbash
ne lit par exemple que des morceaux de 128 octets, c’est-à-dire encore beaucoup moins que les utilitaires de texte feront).Idem côté sortie,
echo
ne peut pas simplement mettre sa sortie en mémoire tampon, il doit la sortir immédiatement car la prochaine commande que vous exécuterez ne partagera pas cette mémoire tampon.Évidemment, exécuter les commandes de manière séquentielle signifie que vous devez les attendre, c'est une petite danse de planificateur qui donne le contrôle depuis le shell aux outils. Cela signifie également (par opposition à l'utilisation d'instances longues dans un pipeline) que vous ne pouvez pas exploiter plusieurs processeurs en même temps lorsqu'ils sont disponibles.
Entre cette
while read
boucle et l'équivalent (supposé)cut -c3 < file
, dans mon test rapide, il y a un rapport de temps de processeur d'environ 40000 lors de mes tests (une seconde par rapport à une demi-journée). Mais même si vous utilisez uniquement des commandes intégrées au shell:(ici avec
bash
), cela reste autour de 1: 600 (une seconde contre 10 minutes).Fiabilité / lisibilité
Il est très difficile d'obtenir ce code correctement. Les exemples que j'ai donnés sont trop souvent vus à l'état sauvage, mais ils ont beaucoup d'insectes.
read
est un outil pratique qui peut faire beaucoup de choses différentes. Il peut lire les entrées de l'utilisateur, les diviser en mots pour les stocker dans différentes variables.read line
ne lit pas une ligne d'entrée, ou peut-être lit-il une ligne d'une manière très spéciale. En fait, il lit les mots de l'entrée, ces mots séparés par une$IFS
barre oblique inversée pouvant être utilisée pour échapper aux séparateurs ou au caractère de nouvelle ligne.Avec la valeur par défaut de
$IFS
, sur une entrée comme:read line
va stocker"foo/bar baz"
dans$line
, pas" foo\/bar \"
comme vous le souhaitiez.Pour lire une ligne, vous avez besoin de:
Ce n’est pas très intuitif, mais c’est comme ça, souvenez-vous que les coquillages ne devaient pas être utilisés de la sorte.
Pareil pour
echo
.echo
étend les séquences. Vous ne pouvez pas l'utiliser pour des contenus arbitraires comme le contenu d'un fichier aléatoire. Vous avez besoinprintf
ici à la place.Et bien sûr, il y a l' oubli typique de citer votre variable dans laquelle tout le monde tombe. Donc c'est plus:
Maintenant, quelques mises en garde supplémentaires:
zsh
, cela ne fonctionne pas si l'entrée contient des caractères NUL alors qu'au moins les utilitaires de texte GNU n'auraient pas le problème.Si nous voulons aborder certaines de ces questions ci-dessus, cela devient:
Cela devient de moins en moins lisible.
La transmission de données à des commandes via les arguments ou l'extraction de leur sortie dans des variables soulève un certain nombre d'autres problèmes:
-
(ou+
parfois)expr
,test
...Considérations de sécurité
Lorsque vous commencez à utiliser des variables shell et des arguments de commandes , vous entrez dans un champ de mines.
Si vous oubliez de citer vos variables , oubliez le marqueur de fin d'option , travaillez dans des environnements locaux avec des caractères multi-octets (la norme de nos jours), vous êtes certain de présenter des bogues qui deviendront tôt ou tard des vulnérabilités.
Quand vous voudrez peut-être utiliser des boucles.
À déterminer
la source
cut
par exemple, est efficace.cut -f1 < a-very-big-file
est efficace, aussi efficace que vous le feriez si vous l'écriviez en C. Ce qui est terriblement inefficace et sujet à des erreurs est l'appelantcut
pour chaque ligne d'unea-very-big-file
boucle dans un shell, ce qui est le but de cette réponse. Cela concorde avec votre dernière déclaration sur l'écriture de code inutile qui me laisse penser que je ne comprends peut-être pas votre commentaire.En ce qui concerne le concept et la lisibilité, les coques s'intéressent généralement aux fichiers. Leur "unité adressable" est le fichier et "adresse" le nom du fichier. Les shells ont toutes sortes de méthodes pour tester l'existence, le type et le formatage du nom de fichier (commençant par globbing). Les shell ont très peu de primitives pour traiter le contenu du fichier. Les programmeurs shell doivent faire appel à un autre programme pour gérer le contenu des fichiers.
En raison de l'orientation des fichiers et des noms de fichiers, la manipulation de texte dans le shell est très lente, comme vous l'avez noté, mais nécessite également un style de programmation peu clair et contourné.
la source
Il y a des réponses compliquées, donnant beaucoup de détails intéressants aux geeks parmi nous, mais c'est vraiment très simple - le traitement d'un fichier volumineux dans une boucle de shell est tout simplement trop lent.
Je pense que le questionneur est intéressant dans un type de script shell typique, qui peut commencer par une analyse syntaxique en ligne de commande, un paramètre d’environnement, la vérification des fichiers et des répertoires et un peu plus d’initialisation, avant de passer à la tâche principale. fichier texte orienté ligne.
Pour les premières parties (
initialization
), peu importe que les commandes du shell soient lentes - cela n’exécute que quelques douzaines de commandes, peut-être avec quelques boucles courtes. Même si nous écrivons cette partie de manière inefficace, il faudra généralement moins d'une seconde pour effectuer toute cette initialisation, et c'est très bien, cela ne se produit qu'une fois.Mais lorsque nous traitons le gros fichier, qui peut contenir des milliers, voire des millions, de lignes, il n’est pas normal que le script shell prenne une fraction de seconde significative (même s’il ne s’agit que de quelques dizaines de millisecondes), car cela pourrait s’ajouter aux heures.
C’est à ce moment-là que nous devons utiliser d’autres outils, et la beauté des scripts de shell Unix est qu’ils nous permettent de le faire très facilement.
Au lieu d'utiliser une boucle pour examiner chaque ligne, nous devons passer le fichier entier à travers un pipeline de commandes . Cela signifie qu'au lieu d'appeler les commandes des milliers, voire des millions de fois, le shell les appelle une seule fois. Il est vrai que ces commandes auront des boucles pour traiter le fichier ligne par ligne, mais ce ne sont pas des scripts shell, elles sont conçues pour être rapides et efficaces.
Unix a de nombreux outils intégrés, du plus simple au plus complexe, que nous pouvons utiliser pour construire nos pipelines. Je commencerais généralement par les plus simples et n'utiliserais que les plus complexes lorsque cela est nécessaire.
J'essayerais également de m'en tenir aux outils standard disponibles sur la plupart des systèmes et de garder mon utilisation portable, bien que ce ne soit pas toujours possible. Et si votre langue préférée est Python ou Ruby, peut-être que l’effort supplémentaire de vous assurer qu’il est installé sur chaque plate-forme sur laquelle votre logiciel doit être exécuté ne vous dérangera peut-être pas :-)
Des outils simples comprennent
head
,tail
,grep
,sort
,cut
,tr
,sed
,join
(lors de la fusion 2 fichiers), etawk
une seule ligne, parmi beaucoup d' autres. C'est incroyable ce que certaines personnes peuvent faire avec le filtrage et lessed
commandes.Lorsque la situation devient plus complexe et que vous devez appliquer une logique à chaque ligne,
awk
c’est une bonne option - soit une ligne unique (certaines personnes placent des scripts awk entiers dans «une ligne», bien que ce ne soit pas très lisible), ou bien une court script externe.En
awk
tant que langage interprété (comme votre shell), il est étonnant qu’il puisse traiter le traitement ligne par ligne de manière aussi efficace, mais il est conçu à cet effet et est vraiment très rapide.Et puis, il existe
Perl
un grand nombre d'autres langages de script qui sont très efficaces pour le traitement de fichiers texte et qui comportent également de nombreuses bibliothèques utiles.Et enfin, il y a le bon vieux C, si vous avez besoin d' une vitesse maximale et d'une grande flexibilité (bien que le traitement de texte soit un peu fastidieux). Mais c'est probablement une très mauvaise utilisation de votre temps pour écrire un nouveau programme C pour chaque tâche de traitement de fichier que vous rencontrez. Je travaille beaucoup avec des fichiers CSV, j'ai donc écrit plusieurs utilitaires génériques en C que je peux réutiliser dans de nombreux projets différents. En fait, cela élargit la gamme des «outils Unix simples et rapides» que je peux appeler à partir de scripts shell, ce qui me permet de gérer la plupart des projets en écrivant uniquement des scripts, ce qui est beaucoup plus rapide que l'écriture et le débogage de code C personnalisé à chaque fois!
Quelques dernières astuces:
export LANG=C
, sinon de nombreux outils traiteront vos fichiers plain-old-ASCII comme des caractères Unicode, ce qui les ralentira beaucoupexport LC_ALL=C
si vous souhaitezsort
obtenir un ordre cohérent, quel que soit l'environnement!sort
vos données, cela prendra probablement plus de temps (et de ressources: processeur, mémoire, disque), alors essayez de minimiser le nombre desort
commandes et la taille des fichiers à trier.la source
Oui mais...
La réponse de Stéphane Chazelas est basé sur shell concept de déléguer toutes les opérations de texte à des binaires spécifiques, comme
grep
,awk
,sed
et d' autres.Comme bash est capable de faire beaucoup de choses par lui-même, il peut être plus rapide de laisser tomber des fourchettes (même que de faire appel à un autre interprète pour tout faire).
Pour un exemple, jetez un coup d'oeil sur ce post:
https://stackoverflow.com/a/38790442/1765658
et
https://stackoverflow.com/a/718000078/1765658
tester et comparer ...
Bien sûr
Il n'y a aucune considération pour la saisie de l'utilisateur et la sécurité !
N'écrivez pas d'application web sous bash !!
Mais pour de nombreuses tâches d’administration de serveur, où bash pourrait être utilisé à la place d’un shell , l’utilisation de basins intégré pourrait être très efficace.
Ma signification:
Écrire des outils comme bin utils n’est pas du même genre de travail que l’administration système.
Donc pas les mêmes personnes!
Là où les administrateurs système doivent savoir
shell
, ils pourraient écrire des prototypes en utilisant son outil préféré (et le plus connu).Si ce nouvel utilitaire (prototype) est vraiment utile, d'autres personnes pourraient développer un outil dédié en utilisant un langage plus approprié.
la source
bash
bash
zsh
read
sh
, awk , sed ,grep
,ed
,ex
,cut
,sort
,join
... le tout avec plus de fiabilité que Bash ou Perl.bash
installation par défaut.bash
est la plupart du temps trouvé que sur Apple et les systèmes GNU macOS (je suppose que ce que vous appelez les principales distributions ), bien que de nombreux systèmes ont aussi comme un ensemble en option (commezsh
,tcl
,python
...)