Commande pour ajouter une chaîne à chaque ligne?

36

Vous cherchez quelque chose comme ça? Des idées?

cmd | prepend "[ERRORS] "

[ERROR] line1 text
[ERROR] line2 text
[ERROR] line3 text
... etc
utilisateur14645
la source
Est-il possible de le configurer pour toutes les commandes de la fonction / script bash?
Alexander Mills

Réponses:

39
cmd | while read line; do echo "[ERROR] $line"; done

a l'avantage d'utiliser uniquement les commandes intégrées bash, de sorte que moins de processus seront créés / détruits, ce qui devrait être plus rapide que awk ou sed.

@tzrik fait remarquer que cela pourrait aussi faire une belle fonction bash. Définir comme:

function prepend() { while read line; do echo "${1}${line}"; done; }

lui permettrait d'être utilisé comme:

cmd | prepend "[ERROR] "
pjz
la source
4
Cela ne fait en réalité que réduire le nombre de processus par un. (Mais cela pourrait être plus rapide, car aucune expression rationnelle ( sed) ni même stringitting ( awk) n'est utilisé.)
grawity
BTW, j'étais curieux de performance et voici les résultats de mon benchmark simple en utilisant bash, sed et awk. Transférer environ 1000 lignes de texte (sortie dmesg) dans un fichier FIFO, puis les lire comme ceci: pastebin.ca/1606844 On dirait que awk est le gagnant. Des idées pourquoi?
Ilya Zakreuski
1
soyez prudent en exécutant des tests de synchronisation comme ceux-ci - essayez de les exécuter dans les 6 ordres différents, puis de faire la moyenne des résultats. Différents ordres pour atténuer les effets du cache de blocs et moyenne pour atténuer les effets d'interruption / de planification en arrière-plan.
pjz
Cette question est étiquetée "shell", pas "bash".
fiatjaf
1
Assez facile à envelopper dans une fonction aussi:function prepend() { while read line; do echo "${1}${line}"; done; }
tzrlk
46

Essaye ça:

cmd | awk '{print "[ERROR] " $0}'

À votre santé

Ilya Zakreuski
la source
1
Cela a pour inconvénient que "[ERREUR]" ne peut pas être une variable, car toute l'expression doit être entre guillemets simples.
user1071136
4
awk -vT="[ERROR] " '{ print T $0 }'ouawk -vT="[ERROR]" '{ print T " " $0 }'
Tino
2
T="[ERROR] " awk '{ print ENVIRON["T"] $0 }'ouT="[ERROR]" awk '{ print ENVIRON["T"] " " $0 }'
Tino
Vous pouvez simplement quitter la portée des guillemets pour déréférencer la variable: cmd | awk '{print "['$V]' " $0}'- elle doit être évaluée une fois au début, donc pas de surcharge de performances.
robert
13

Avec tout le mérite de @grawity, je soumets son commentaire comme réponse, car il me semble que cette réponse est la meilleure.

sed 's/^/[ERROR] /' cmd
Eric Wilson
la source
Pourquoi est-ce préférable à la solution bash?
utilisateur14645
1
Je suppose que cela dépend de votre but. Si votre objectif est d’ajouter simplement chaque ligne dans un fichier, vous pouvez atteindre cet objectif avec très peu de caractères et un outil très familier. Je préfère de loin cela à un script bash de 10 lignes. Le awkone-liner est assez sympa, mais je pense que plus de gens connaissent sedque awk. Le script bash est bon pour ce qu'il fait, mais il semble qu'il réponde à une question qui n'a pas été posée.
Eric Wilson
La réponse que pjz a soumise est également un bon one-liner. Il n’ajoute pas de programmes ni de processus supplémentaires et peut être exécuté un peu plus rapidement.
user14645
3
sed X cmdlit cmdet ne l'exécute pas. Ou cmd | sed 's/^/[ERROR] /'ou sed 's/^/[ERROR] /' <(cmd)ou cmd > >(sed 's/^/[ERROR] /'). Mais méfiez-vous de ce dernier. Même que cela vous permet d'accéder à la valeur de retour de cmdla sedfonctionne en arrière - plan, il est donc probable que vous voyez la sortie après cmd terminé. Bon pour vous connecter à un fichier, cependant. Et notez que c'est awkprobablement plus rapide que sed.
Tino
Agréable. Cette commande est facilement aliasée. alias lpad="sed 's/^/ /'". au lieu d'ERREUR, j'insère 4 espaces de début. Maintenant, pour le tour de magie: la ls | lpad | pbcopysortie ls sera préfixée de 4 espaces, ce qui le marque en tant que Markdown pour le code , ce qui signifie que vous collez le presse-papiers ( pbcopy le saisit, sur macs) directement dans StackOverflow ou tout autre contexte de démarquage. Impossible de aliasla awk réponse (le 1er essai) si celui - ci gagne. La while read solution est également alias-mesure, mais je trouve cela sed plus expressif.
JL Peyret
8

J'ai créé un référentiel GitHub pour faire des tests de vitesse.

Le résultat est:

  • Dans le cas général, awkc'est le plus rapide. sedest un peu plus lent et perln'est pas beaucoup plus lent que sed. Apparemment, toutes ces langues sont hautement optimisées pour le traitement de texte.
  • Dans des situations très spéciales, où les forks dominent, exécuter votre script en tant que kshscript compilé ( shcomp) permet de gagner encore plus de temps de traitement. En revanche, il bashest extrêmement lent comparé aux kshscripts compilés .
  • Créer un binaire statiquement lié à battre awkne semble pas en valoir la peine.

En revanche, pythonc'est très lent, mais je n'ai pas testé de cas compilé, car ce n'est généralement pas ce que vous feriez dans un tel cas de script.

Les variantes suivantes sont testées:

while read line; do echo "[TEST] $line"; done
while read -r line; do echo "[TEST] $line"; done
while read -r line; do echo "[TEST]" $line; done
while read -r line; do echo "[TEST]" "$line"; done
sed 's/^/[TEST] /'
awk '{ print "[TEST] " $0 }'
awk -vT="[TEST] " '{ print T $0 }'
awk -vT="[TEST]" '{ print T " " $0 }'
awk -vT="[TEST]" 'BEGIN { T=T " "; } { print T $0 }'
T="[TEST] " awk '{ print ENVIRON["T"] $0 }'
T="[TEST]" awk '{ print ENVIRON["T"] " " $0 }'
T="[TEST]" awk 'BEGIN { T=ENVIRON["T"] " " } { print T $0 }'
perl -ne 'print "[TEST] $_"'

Deux variantes binaires de l'un de mes outils (la vitesse n'est toutefois pas optimisée):

./unbuffered.dynamic -cp'[TEST] ' -q ''
./unbuffered.static -cp'[TEST] ' -q ''

Python tamponné:

python -uSc 'import sys
for line in sys.stdin: print "[TEST]",line,'

Et Python sans tampon:

python -uSc 'import sys
while 1:
 line = sys.stdin.readline()
 if not line: break
 print "[TEST]",line,'
Tino
la source
awk -v T="[TEST %Y%m%d-%H%M%S] " '{ print strftime(T) $0 }'pour émettre un horodatage
Tino le
5
cmd | sed 's/.*/[ERROR] &/'
En pause jusqu'à nouvel ordre.
la source
16
sed 's/^/[ERROR] /'
Grawity
3

Je voulais une solution qui gère stdout et stderr, alors je l'ai écrite prepend.shet mise sur mon chemin:

#!/bin/bash

prepend_lines(){
  local prepended=$1
  while read line; do
    echo "$prepended" "$line"
  done
}

tag=$1

shift

"$@" > >(prepend_lines "$tag") 2> >(prepend_lines "$tag" 1>&2)

Maintenant, je peux juste exécuter prepend.sh "[ERROR]" cmd ..., pour ajouter "[ERREUR]" à la sortie de cmd, et toujours avoir stderr et stdout séparés.

Aprotim
la source
J'ai essayé cette approche mais il y avait quelque chose qui se passait avec ces >(sous-coques que je ne pouvais pas tout à fait résoudre. Il semblait que le script se terminait et que la sortie arrivait au terminal après le retour de l'invite, ce qui était un peu brouillon. J'ai finalement trouvé la réponse ici stackoverflow.com/a/25948606/409638
robert