Polecenie, aby dodać ciąg do każdej linii?

36

Szukasz czegoś takiego? Jakieś pomysły?

cmd | prepend "[ERRORS] "

[ERROR] line1 text
[ERROR] line2 text
[ERROR] line3 text
... etc
użytkownik14645
źródło
Czy jest jakiś sposób, aby ustawić to dla wszystkich poleceń w funkcji / skrypcie bash?
Alexander Mills,

Odpowiedzi:

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

ma tę zaletę, że wykorzystuje tylko wbudowane bash, więc mniej procesów zostanie utworzonych / zniszczonych, więc powinno być odrobinę szybsze niż awk lub sed.

@tzrik wskazuje, że może to również być przyjemna funkcja bash. Definiując to tak:

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

pozwoli na użycie go w następujący sposób:

cmd | prepend "[ERROR] "
pjz
źródło
4
To faktycznie zmniejsza liczbę procesów tylko o jeden. (Ale może być szybsze, ponieważ nie są używane sedawk
wyrażenia
BTW, byłem ciekawy wydajności i oto wyniki mojego prostego testu porównawczego przy użyciu bash, sed i awk. Wepchnięcie około 1000 wierszy tekstu (wyjście dmesg) do pliku FIFO, a następnie czytanie ich w ten sposób: pastebin.ca/1606844 Wygląda na to, że awk jest zwycięzcą. Jakieś pomysły, dlaczego?
Ilya Zakreuski
1
bądź ostrożny, przeprowadzając takie testy czasowe - spróbuj uruchomić je we wszystkich 6 różnych zamówieniach, a następnie uśrednij wyniki. Różne zamówienia w celu złagodzenia efektów pamięci podręcznej bloków i średnie w celu złagodzenia efektów przerwania / planowania w tle.
pjz
To pytanie jest oznaczone jako „shell”, a nie „bash”.
fiatjaf
1
Łatwo jest też zawinąć go w funkcję:function prepend() { while read line; do echo "${1}${line}"; done; }
tzrlk
46

Spróbuj tego:

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

Twoje zdrowie

Ilya Zakreuski
źródło
1
Ma to tę wadę, że „[ERROR]” nie może być zmienną, ponieważ całe wyrażenie musi być w cudzysłowach.
user1071136,
4
awk -vT="[ERROR] " '{ print T $0 }'lubawk -vT="[ERROR]" '{ print T " " $0 }'
Tino
2
T="[ERROR] " awk '{ print ENVIRON["T"] $0 }'lubT="[ERROR]" awk '{ print ENVIRON["T"] " " $0 }'
Tino
Możesz po prostu wyjść z zakresu cytatów, aby wyłuskać zmienną: cmd | awk '{print "['$V]' " $0}'- należy to ocenić raz na początku, aby nie narzucać wydajności.
Robert
13

Z całym uznaniem dla @grawity przesyłam jego komentarz jako odpowiedź, ponieważ wydaje mi się to najlepszą odpowiedzią tutaj.

sed 's/^/[ERROR] /' cmd
Eric Wilson
źródło
Dlaczego jest to lepsze niż rozwiązanie bash?
user14645,
1
Przypuszczam, że to zależy od twojego celu. Jeśli Twoim celem jest po prostu wstawianie do każdej linii w pliku, osiąga ten cel przy użyciu bardzo niewielu znaków, przy użyciu bardzo znanego narzędzia. Zdecydowanie wolę to od 10-wierszowego skryptu bash. awkJedna wkładka jest dość miłe, ale myślę, że więcej ludzi zna sedniż awk. Skrypt bash jest dobry do tego, co robi, ale wydaje się, że odpowiada na pytanie, które nie zostało zadane.
Eric Wilson,
Odpowiedź przesłana przez pjz jest również niezłą linią. Nie powoduje dodatkowych programów, procesów i może działać nieco szybciej.
user14645
3
sed X cmdczyta cmdi nie wykonuje tego. Albo cmd | sed 's/^/[ERROR] /'albo sed 's/^/[ERROR] /' <(cmd)albo cmd > >(sed 's/^/[ERROR] /'). Ale uwaga ta ostatnia. Mimo, że ta pozwala na dostęp do wartości zwracanej cmdprzez seddziała w tle, więc jest prawdopodobne, widać wyjście po cmd gotowych. Dobry do logowania się do pliku. I zauważ, że awkprawdopodobnie jest szybszy niż sed.
Tino
Miły. To polecenie jest łatwo aliasowane. alias lpad="sed 's/^/ /'". zamiast BŁĘDU wstawiam 4 wiodące spacje. Teraz, dla magicznej sztuczki: ls | lpad | pbcopyprzygotuje wyjście ls z 4 spacjami, co oznacza go jako Markdown dla kodu , co oznacza, że ​​wklejasz schowek ( pbcopy chwyta go, na Macach) bezpośrednio w StackOverflow lub w innym kontekście Markdown . Nie udało aliassię odpowiedzieć na awk (za pierwszym razem), więc ten wygrywa. Podczas odczytu rozwiązaniem jest również alias-stanie, ale uważam ten sed bardziej wyraziste.
JL Peyret,
8

Utworzyłem repozytorium GitHub, aby przeprowadzić testy prędkości.

Wynik to:

  • W ogólnym przypadku awkjest najszybszy. sedjest nieco wolniejszy i perlnie jest dużo wolniejszy niż sed. Najwyraźniej wszystkie te są wysoce zoptymalizowanymi językami do przetwarzania tekstu.
  • W bardzo szczególnych sytuacjach, w których dominują widelce, uruchomienie skryptu jako skompilowanego kshskryptu ( shcomp) może zaoszczędzić jeszcze więcej czasu przetwarzania. W przeciwieństwie do tego bashjest dość powolny w porównaniu ze skompilowanymi kshskryptami.
  • Tworzenie statycznie powiązanego pliku binarnego do pokonania awknie wydaje się warte wysiłku.

W przeciwieństwie do tego pythonjest bardzo powolny, ale nie testowałem skompilowanej skrzynki, ponieważ zwykle nie jest to, co zrobiłbyś w takiej skrzynce.

Testowane są następujące warianty:

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

Dwa binarne warianty jednego z moich narzędzi (jednak nie jest zoptymalizowane pod kątem szybkości):

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

Buforowane w Pythonie:

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

Python niebuforowany:

python -uSc 'import sys
while 1:
 line = sys.stdin.readline()
 if not line: break
 print "[TEST]",line,'
Tino
źródło
awk -v T="[TEST %Y%m%d-%H%M%S] " '{ print strftime(T) $0 }'wypisać znacznik czasu
Tino
5
cmd | sed 's/.*/[ERROR] &/'
Wstrzymano do odwołania.
źródło
16
sed 's/^/[ERROR] /'
grawity
3

Chciałem rozwiązania, które obsługiwałoby stdout i stderr, więc napisałem prepend.shi postawiłem na swojej drodze:

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

Teraz mogę po prostu uruchomić prepend.sh "[ERROR]" cmd ..., aby dodać „[ERROR]” do wyjścia cmdi nadal mieć osobne stderr i stdout.

Aprotim
źródło
Wypróbowałem to podejście, ale z tymi >(powłokami wewnętrznymi działo się coś , czego nie mogłem całkowicie rozwiązać. Wydawało się, że skrypt się kończy, a dane wyjściowe docierają do terminala po powrocie monitu, co było nieco nieuporządkowane. W końcu znalazłem odpowiedź tutaj stackoverflow.com/a/25948606/409638
Robert