J'essaie de trouver le moyen le plus efficace d'itérer à travers certaines valeurs qui sont un nombre cohérent de valeurs éloignées les unes des autres dans une liste de mots séparés par des espaces (je ne veux pas utiliser un tableau). Par exemple,
list="1 ant bat 5 cat dingo 6 emu fish 9 gecko hare 15 i j"
Je veux donc pouvoir simplement parcourir la liste et accéder uniquement aux 1,5,6,9 et 15.
EDIT: J'aurais dû indiquer clairement que les valeurs que j'essaie d'obtenir de la liste ne doivent pas être de format différent du reste de la liste. Ce qui les rend spéciaux, c'est uniquement leur position dans la liste (dans ce cas, position 1,4,7 ...). Donc, la liste pourrait être,1 2 3 5 9 8 6 90 84 9 3 2 15 75 55
mais je voudrais toujours les mêmes chiffres. Et aussi, je veux pouvoir le faire en supposant que je ne connais pas la longueur de la liste.
Les méthodes auxquelles j'ai pensé jusqu'à présent sont les suivantes:
Méthode 1
set $list
found=false
find=9
count=1
while [ $count -lt $# ]; do
if [ "${@:count:1}" -eq $find ]; then
found=true
break
fi
count=`expr $count + 3`
done
Méthode 2
set list
found=false
find=9
while [ $# ne 0 ]; do
if [ $1 -eq $find ]; then
found=true
break
fi
shift 3
done
Méthode 3 Je suis presque sûr que la tuyauterie en fait la pire option, mais j'essayais de trouver une méthode qui n'utilise pas set, par curiosité.
found=false
find=9
count=1
num=`echo $list | cut -d ' ' -f$count`
while [ -n "$num" ]; do
if [ $num -eq $find ]; then
found=true
break
fi
count=`expr $count + 3`
num=`echo $list | cut -d ' ' -f$count`
done
Alors, qu'est-ce qui serait le plus efficace ou manque-t-il une méthode plus simple?
la source
Réponses:
Assez simple avec
awk
. Cela vous donnera la valeur de chaque quatrième champ pour une entrée de n'importe quelle longueur:Cela fonctionne en tirant parti des
awk
variables intégrées telles queNF
(le nombre de champs dans l'enregistrement) et en effectuant unefor
boucle simple pour parcourir les champs pour vous donner celles que vous voulez sans avoir besoin de savoir à l'avance combien il y en aura.Ou, si vous voulez en effet simplement ces champs spécifiques comme spécifié dans votre exemple:
En ce qui concerne la question de l'efficacité, la voie la plus simple serait de tester cette ou chacune de vos autres méthodes et de l'utiliser
time
pour montrer combien de temps cela prend; vous pouvez également utiliser des outils tels questrace
pour voir le flux des appels système. L'utilisation detime
ressemble à:Vous pouvez comparer cette sortie entre différentes méthodes pour voir laquelle est la plus efficace en termes de temps; d'autres outils peuvent être utilisés pour d'autres mesures d'efficacité.
la source
echo
vs<<<
, "identique" est un mot trop fort. On pourrait dire questuff <<< "$list"
c'est presque identique àprintf "%s\n" "$list" | stuff
. Concernantecho
vsprintf
, je vous<<<
ajoute une nouvelle ligne à la fin. Ceci est similaire à la façon dont$()
supprime une nouvelle ligne de la fin. En effet, les lignes se terminent par des retours à la ligne.<<<
alimente une expression sous forme de ligne, elle doit donc se terminer par une nouvelle ligne."$()"
prend des lignes et les fournit comme argument, il est donc logique de convertir en supprimant la nouvelle ligne de fin.awk
c'est un binaire autonome qui doit démarrer. Contrairement à Perl ou à Python en particulier, l'interpréteur awk démarre rapidement (toujours tous les frais généraux de l'éditeur de liens dynamiques pour faire pas mal d'appels système, mais awk n'utilise que libc / libm et libdl. Par exemple, utilisezstrace
pour vérifier les appels système du démarrage awk) . De nombreux shells (comme bash) sont assez lents, donc lancer un processus awk peut être plus rapide que de boucler sur des jetons dans une liste avec des shell intégrés même pour des tailles de liste de petite taille. Et parfois, vous pouvez écrire un#!/usr/bin/awk
script au lieu d'un#!/bin/sh
script.Première règle d'optimisation logicielle: ne le faites pas .
Tant que vous ne savez pas que la vitesse du programme est un problème, il n'est pas nécessaire de penser à quelle vitesse il est. Si votre liste est de cette longueur ou seulement de 100 à 1000 articles, vous ne remarquerez probablement même pas combien de temps cela prend. Il est possible que vous passiez plus de temps à penser à l'optimisation qu'à la différence.
Deuxième règle: mesurer .
C'est le moyen sûr de le savoir et celui qui donne des réponses pour votre système. Surtout avec des coquillages, il y en a tellement, et ils ne sont pas tous identiques. Une réponse pour un shell peut ne pas s'appliquer à la vôtre.
Dans les programmes plus importants, le profilage va ici aussi. La partie la plus lente n'est peut-être pas celle que vous pensez.
Troisièmement, la première règle d'optimisation du script shell: n'utilisez pas le shell .
Ouais vraiment. De nombreux shells ne sont pas faits pour être rapides (car le lancement de programmes externes ne doit pas l'être), et ils peuvent même analyser à nouveau les lignes du code source à chaque fois.
Utilisez plutôt quelque chose comme awk ou Perl. Dans un micro-benchmark trivial que j'ai fait,
awk
était des dizaines de fois plus rapide que n'importe quel shell commun pour exécuter une boucle simple (sans E / S).Cependant, si vous utilisez le shell, utilisez les fonctions internes du shell au lieu des commandes externes. Ici, vous utilisez
expr
ce qui n'est intégré à aucun shell que j'ai trouvé sur mon système, mais qui peut être remplacé par une expansion arithmétique standard. Par exemple,i=$((i+1))
au lieu d'i=$(expr $i + 1)
incrémenteri
. Votre utilisation decut
dans le dernier exemple peut également être remplaçable par des extensions de paramètres standard.Voir aussi: Pourquoi l'utilisation d'une boucle shell pour traiter du texte est-elle considérée comme une mauvaise pratique?
Les étapes 1 et 2 doivent s'appliquer à votre question.
la source
awk
boucles soient nécessairement meilleures ou pires que les boucles shell. C'est que le shell est vraiment bon pour exécuter des commandes et pour diriger les entrées et sorties vers et depuis les processus, et franchement plutôt maladroit pour tout le reste; alors que les outils commeawk
sont fantastiques pour traiter les données de texte, parce que c'est pour cela que les coquilles et les outilsawk
sont conçus (respectivement) en premier lieu.dash
qu'avecgawk
, etdash
c'était le shell le plus rapide que j'ai testé ...dash
etbusybox
ne supporte pas(( .. ))
- je pense que c'est une extension non standard.++
est également mentionné explicitement comme non requis, pour autant que je sache,i=$((i+1))
ou: $(( i += 1))
sont les plus sûrs.Je vais seulement donner quelques conseils généraux dans cette réponse, et non des repères. Les repères sont le seul moyen de répondre de manière fiable aux questions sur les performances. Mais comme vous ne dites pas la quantité de données que vous manipulez et la fréquence à laquelle vous effectuez cette opération, il n'y a aucun moyen de faire un benchmark utile. Ce qui est plus efficace pour 10 éléments et ce qui est plus efficace pour 1000000 éléments n'est souvent pas le même.
En règle générale, invoquer des commandes externes coûte plus cher que de faire quelque chose avec des constructions de shell pur, tant que le code de shell pur n'implique pas de boucle. D'un autre côté, une boucle shell qui itère sur une grande chaîne ou une grande quantité de chaîne est susceptible d'être plus lente qu'une invocation d'un outil spécial. Par exemple, votre appel de boucle
cut
pourrait être sensiblement lent dans la pratique, mais si vous trouvez un moyen de faire le tout avec une seulecut
invocation, cela sera probablement plus rapide que de faire la même chose avec la manipulation de chaînes dans le shell.Notez que le point de coupure peut varier considérablement d'un système à l'autre. Cela peut dépendre du noyau, de la configuration de l'ordonnanceur du noyau, du système de fichiers contenant les exécutables externes, de la quantité de pression CPU vs mémoire actuellement, et de nombreux autres facteurs.
N'appelez pas
expr
pour effectuer de l'arithmétique si vous êtes préoccupé par la performance. En fait, n'appelez pas du toutexpr
pour effectuer de l'arithmétique. Les shells ont une arithmétique intégrée, qui est plus claire et plus rapide que l'invocationexpr
.Vous semblez utiliser bash, puisque vous utilisez des constructions bash qui n'existent pas dans sh. Alors pourquoi diable n'utilisez-vous pas un tableau? Un tableau est la solution la plus naturelle, et il est probable qu'elle soit aussi la plus rapide. Notez que les indices de tableau commencent à 0.
Votre script pourrait bien être plus rapide si vous utilisez sh, si votre système a dash ou ksh
sh
plutôt que bash. Si vous utilisez sh, vous n'obtenez pas de tableaux nommés, mais vous obtenez tout de même un tableau de paramètres positionnels, que vous pouvez définir avecset
. Pour accéder à un élément à une position qui n'est pas connue avant l'exécution, vous devez utilisereval
(prenez soin de citer les choses correctement!).Si vous ne souhaitez accéder au tableau qu'une seule fois et que vous allez de gauche à droite (en sautant certaines valeurs), vous pouvez utiliser à la
shift
place des indices de variable.L'approche la plus rapide dépend du shell et du nombre d'éléments.
Une autre possibilité consiste à utiliser le traitement de chaîne. Il a l'avantage de ne pas utiliser les paramètres de position, vous pouvez donc les utiliser pour autre chose. Ce sera plus lent pour de grandes quantités de données, mais il est peu probable que cela fasse une différence notable pour de petites quantités de données.
la source
shift && shift && shift
parshift 3
dans votre troisième exemple - sauf si le shell que vous utilisez ne le prend pas en charge.shift 3
échouerait s'il restait trop peu d'arguments. Vous auriez besoin de quelque chose commeif [ $# -gt 3 ]; then shift 3; else set --; fi
awk
est un excellent choix, si vous pouvez faire tout votre traitement à l'intérieur du script Awk. Sinon, vous finissez par diriger la sortie Awk vers d'autres utilitaires, détruisant le gain de performances deawk
.bash
l'itération sur un tableau est également excellente, si vous pouvez insérer toute votre liste à l'intérieur du tableau (ce qui pour les shells modernes est probablement une garantie) et cela ne vous dérange pas la gymnastique de la syntaxe du tableau.Cependant, une approche de pipeline:
Où:
xargs
regroupe la liste séparée par des espaces en lots de trois, chaque nouvelle ligne étant séparéewhile read
consomme cette liste et affiche la première colonne de chaque groupegrep
filtre la première colonne (correspondant à chaque troisième position dans la liste d'origine)Améliore la compréhensibilité, à mon avis. Les gens savent déjà ce que font ces outils, il est donc facile de lire de gauche à droite et de raisonner sur ce qui va se passer. Cette approche documente également clairement la longueur de foulée (
-n3
) et le modèle de filtre (9
), il est donc facile de la varier:Lorsque nous posons des questions sur «l'efficacité», pensez à «l'efficacité totale de la durée de vie». Ce calcul inclut l'effort des mainteneurs pour maintenir le code en fonctionnement, et nous les sacs de viande sont les machines les moins efficaces de toute l'opération.
la source
Peut-être cela?
la source
N'utilisez pas de commandes shell si vous voulez être efficace. Limitez-vous aux canaux, aux redirections, aux substitutions, etc. et aux programmes. C'est pourquoi
xargs
et lesparallel
utilitaires existent - parce que les boucles bash while sont inefficaces et très lentes. Utilisez les boucles bash uniquement comme dernière résolution.Mais vous devriez probablement être un peu plus rapide avec un bon
awk
.la source
À mon avis, la solution la plus claire (et probablement la plus performante aussi) est d'utiliser les variables awk RS et ORS:
la source
Utilisation du script shell GNU
sed
et POSIX :Ou avec
bash
la substitution de paramètres de :Non- GNU ( c'est-à-dire POSIX )
sed
etbash
:Ou plus facilement, en utilisant à la fois POSIX
sed
et un script shell:Sortie de l'un de ces éléments:
la source