Befehl, jeder Zeile einen String voran zu stellen?

36

Suchen Sie so etwas? Irgendwelche Ideen?

cmd | prepend "[ERRORS] "

[ERROR] line1 text
[ERROR] line2 text
[ERROR] line3 text
... etc
user14645
quelle
Gibt es eine Möglichkeit, dies für alle Befehle in bash function / script festzulegen?
Alexander Mills

Antworten:

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

hat den Vorteil , nur bash builtins mit so werden weniger Prozesse erstellt / zerstört werden , damit es soll einen Hauch schneller als awk oder sed sein.

@tzrik weist darauf hin, dass es auch eine nette Bash-Funktion machen könnte. Definiere es so:

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

würde es erlauben, verwendet zu werden wie:

cmd | prepend "[ERROR] "
pjz
quelle
4
Dies reduziert die Prozessanzahl tatsächlich nur um eins. (Aber es könnte schneller sein, weil keine regulären Ausdrücke ( sed) oder gar Zeichenfolgenteilung ( awk) verwendet werden.)
Grawity
Übrigens, ich war neugierig auf die Leistung und hier sind die Ergebnisse meines einfachen Benchmarks mit bash, sed und awk. Schieben Sie ungefähr 1000 Textzeilen (dmesg-Ausgabe) in die FIFO-Datei und lesen Sie sie dann so: pastebin.ca/1606844 Sieht aus, als wäre awk der Gewinner. Irgendwelche Ideen warum?
Ilya Zakreuski
1
Seien Sie vorsichtig, wenn Sie solche Timing-Tests durchführen - versuchen Sie, sie in allen 6 verschiedenen Reihenfolgen auszuführen und dann den Durchschnitt der Ergebnisse zu bilden. Unterschiedliche Befehle zum Reduzieren von Blockcache-Effekten und zum Reduzieren von Hintergrundunterbrechungs- / Zeitplanungseffekten.
pjz
Diese Frage hat den Tag "shell" und nicht "bash".
Fiatjaf
1
Einfach genug, um es auch in eine Funktion zu function prepend() { while read line; do echo "${1}${line}"; done; }
packen
46

Versuche dies:

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

Prost

Ilya Zakreuski
quelle
1
Dies hat den Nachteil, dass "[ERROR]" keine Variable sein kann, da der gesamte Ausdruck in einfachen Anführungszeichen stehen muss.
user1071136
4
awk -vT="[ERROR] " '{ print T $0 }'oderawk -vT="[ERROR]" '{ print T " " $0 }'
Tino
2
T="[ERROR] " awk '{ print ENVIRON["T"] $0 }'oderT="[ERROR]" awk '{ print ENVIRON["T"] " " $0 }'
Tino
Sie können den Bereich der Anführungszeichen einfach verlassen, um die Variable zu dereferenzieren: cmd | awk '{print "['$V]' " $0}'- Dies sollte zu Beginn einmal ausgewertet werden, damit kein Leistungsaufwand entsteht.
Robert
13

Bei aller Dankbarkeit für @grawity sende ich seinen Kommentar als Antwort, da er mir hier die beste Antwort zu sein scheint.

sed 's/^/[ERROR] /' cmd
Eric Wilson
quelle
Warum ist dies der bash-Lösung vorzuziehen?
user14645
1
Ich nehme an, es hängt von Ihrem Zweck ab. Wenn Sie einfach jede Zeile in einer Datei voranstellen möchten, können Sie dieses Ziel mit einem sehr vertrauten Werkzeug mit sehr wenigen Zeichen erreichen. Ich ziehe das einem 10-zeiligen Bash-Skript bei weitem vor. Der awkEinzeiler ist nett genug, aber ich denke, dass mehr Leute damit vertraut sind sedals awk. Das Bash-Skript ist gut für das, was es tut, aber es scheint, dass es eine Frage beantwortet, die nicht gestellt wurde.
Eric Wilson
Die Antwort, die pjz eingereicht hat, ist auch ein netter Einzeiler. Es werden keine zusätzlichen Programme, Prozesse und möglicherweise etwas schneller ausgeführt.
user14645
3
sed X cmdliest cmdund führt es nicht aus. Entweder cmd | sed 's/^/[ERROR] /'oder sed 's/^/[ERROR] /' <(cmd)oder cmd > >(sed 's/^/[ERROR] /'). Aber hüte dich vor Letzterem. Auch wenn Sie auf diese Weise auf den Rückgabewert cmdder sedLäufe im Hintergrund zugreifen können , wird die Ausgabe wahrscheinlich nach Abschluss von cmd angezeigt. Gut, um sich in eine Datei einzuloggen. Und beachten Sie, dass awkwahrscheinlich schneller ist als sed.
Tino
Nett. Dieser Befehl ist leicht zu verfälschen. alias lpad="sed 's/^/ /'". anstelle von ERROR füge ich 4 führende Leerzeichen ein. Nun zum Zaubertrick: ls | lpad | pbcopyStellt ls Ausgabe 4 Leerzeichen voran , wodurch es als Markdown für Code gekennzeichnet wird. Dies bedeutet, dass Sie die Zwischenablage ( pbcopy greift danach, auf Macs) direkt in StackOverflow oder einen anderen Markdown-Kontext einfügen. Konnte aliasdie awk nicht antworten (beim ersten Versuch), so dass dieser gewinnt. Die während Lese Lösung ist auch Alias-fähig, aber ich finde diesen sed ausdrucksvoller .
JL Peyret
8

Ich habe ein GitHub-Repository erstellt , um einige Geschwindigkeitstests durchzuführen.

Das Ergebnis ist:

  • Im Allgemeinen awkist am schnellsten. sedist etwas langsamer und perlnicht viel langsamer als sed. Anscheinend sind das alles hochoptimierte Sprachen für die Textverarbeitung.
  • In ganz besonderen Situationen, in denen Gabeln dominieren, kann das Ausführen Ihres Skripts als kompiliertes kshSkript ( shcomp) noch mehr Verarbeitungszeit sparen. Im Gegensatz dazu bashist es im Vergleich zu kompilierten kshSkripten absolut langsam .
  • Das Erstellen einer statisch verknüpften Binärdatei zu schlagen awkscheint die Mühe nicht wert zu sein.

Im Gegensatz dazu pythonist es absolut langsam, aber ich habe einen kompilierten Fall nicht getestet, da es normalerweise nicht das ist, was Sie in einem solchen Skriptfall tun würden.

Folgende Varianten werden getestet:

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] $_"'

Zwei binäre Varianten eines meiner Tools (es ist jedoch nicht für die Geschwindigkeit optimiert):

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

Python gepuffert:

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

Und Python ungepuffert:

python -uSc 'import sys
while 1:
 line = sys.stdin.readline()
 if not line: break
 print "[TEST]",line,'
Tino
quelle
awk -v T="[TEST %Y%m%d-%H%M%S] " '{ print strftime(T) $0 }'um einen Zeitstempel auszugeben
Tino
5
cmd | sed 's/.*/[ERROR] &/'
Bis auf weiteres angehalten.
quelle
16
sed 's/^/[ERROR] /'
Grawity
3

Ich wollte eine Lösung, die stdout und stderr handhabt, also schrieb ich prepend.shund stellte sie in meinen Weg:

#!/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)

Jetzt kann ich einfach ausführen prepend.sh "[ERROR]" cmd ..., um "[ERROR]" vor die Ausgabe von zu stellen cmd, und habe immer noch stderr und stdout getrennt.

Aprotim
quelle
Ich habe diesen Ansatz ausprobiert, aber mit diesen >(Unterschalen war etwas los , das ich nicht ganz lösen konnte. Es schien, als wäre das Skript abgeschlossen und die Ausgabe erreichte das Terminal, nachdem die Eingabeaufforderung zurückgekehrt war, was ein wenig chaotisch war. Ich kam schließlich hier mit der Antwort auf stackoverflow.com/a/25948606/409638
robert