Jak przekierować stdout do pliku, a stdout + stderr do innego?

32

Jak mogę to osiągnąć?

cmd >> file1 2>&1 1>>file2

Oznacza to, że stdout i stderr powinny przekierowywać do jednego pliku (plik1) i tylko stdout (plik2) powinien przekierowywać do innego (oba w trybie dołączania)?

Swarna Gowri
źródło

Odpowiedzi:

41

Problem polega na tym, że po przekierowaniu danych wyjściowych nie jest już dostępny dla następnego przekierowania. Możesz teepotokować do podpowłoki, aby zachować dane wyjściowe dla drugiego przekierowania:

( cmd | tee -a file2 ) >> file1 2>&1

lub jeśli chcesz zobaczyć dane wyjściowe w terminalu:

( cmd | tee -a file2 ) 2>&1 | tee -a file1

Aby uniknąć dodania stderr pierwszego teedo file1, powinieneś przekierować stderr swojego polecenia do jakiegoś deskryptora pliku (np. 3), a następnie dodać to ponownie do stdout:

( 2>&3 cmd | tee -a file2 ) >> file1 3>&1
# or
( 2>&3 cmd | tee -a file2 ) 3>&1 | tee -a file1

(dzięki @ fra-san)

pLumo
źródło
16

Z zsh:

cmd >& out+err.log > out.log

W trybie dołączania:

cmd >>& out+err.log >> out.log

W zshi pod warunkiem, że mult_iosopcja nie została wyłączona, gdy deskryptor pliku (tutaj 1) jest kilkakrotnie przekierowywany do zapisu, wówczas powłoka implementuje wbudowaną funkcję teekopiowania danych wyjściowych do wszystkich celów.

Stéphane Chazelas
źródło
Nie mogę dowiedzieć się, co out+erri outtu na myśli. Nazwy plików Strumienie do przekierowania?
gronostaj
@gronostaj Myśl, że polecenie brzmicmd >& file1 > file2
Izaak
To rozwiązanie zachowa kolejność, w jakiej wygenerowano dane wyjściowe. Aby faktycznie zapisać stdout i stderr (w tej kolejności), potrzebujesz innego podejścia.
Izaak
1
@Isaac, kolejność niekoniecznie zostanie zachowana, ponieważ wyjście stdout przejdzie przez potok (do procesu, który przekazuje go do każdego pliku), a wyjście stderr przejdzie bezpośrednio do pliku. W każdym razie nie wygląda na to, że OP poprosił o wyjście stderr po stdout.
Stéphane Chazelas
3

Możesz: otagować stdout (używając sedera UNBUFFERED, tj .:) sed -u ..., aby stderr również przeszedł do stdout (nieoznaczony, ponieważ nie przeszedł przez tagowania sed), a tym samym móc rozróżnić 2 w wynikowym pliku logu.

Następujące: jest powolny (można go poważnie zoptymalizować, używając na przykład skryptu perl zamiast while ...; do ...; zrobiono, na przykład, który będzie tworzył podpowłoki i polecenia w każdym wierszu!), Dziwne (wydaje się, że potrzebuję 2 {} etapów, aby w jednym zmienić nazwę stdout, a następnie w drugim dodać do niego „przewrócony” stderr), itd. Ale to jest: „ dowód koncepcji ”, który będzie próbował zachować porządek wyjścia jak najwięcej stdout i stderr:

#basic principle (some un-necessary "{}" to visually help see the layers):
# { { complex command ;} | sed -e "s/^/TAGstdout/" ;} 2>&1 | read_stdin_and_redispatch

#exemple:
# complex command = a (slowed) ls of several things (some existing, others not)
#  to see if the order of stdout&stderr is kept

#preparation, not needed for the "proof of concept", but needed for our specific exemple setup:
\rm out.file out_AND_err.file unknown unknown2 
touch existing existing2 existing3

#and the (slow, too many execs, etc) "proof of concept":
uniquetag="_stdout_" # change this to something unique, that will NOT appear in all the commands outputs... 
                     # avoid regexp characters ("+" "?" "*" etc) to make it easy to remove with another sed later on.

{
   { for f in existing unknown existing2 unknown2 existing3 ; do ls -l "$f" ; sleep 1; done ;
   } | sed -u -e "s/^/${uniquetag}/" ;
} 2>&1 | while IFS="" read -r line ; do
    case "$line" in
       ${uniquetag}*) printf "%s\n" "$line" | tee -a out_AND_err.file | sed -e "s/^${uniquetag}//" >> out.file ;; 
        *)            printf "%s\n" "$line"       >> out_AND_err.file ;;   
    esac; 
done;

# see the results:
grep "^" out.file out_AND_err.file
Olivier Dulac
źródło
To jest naprawdę trudne do zrozumienia. (1) Dlaczego używasz tak skomplikowanego przypadku użycia ( ls unknown), aby wydrukować coś na stderr? >&2 echo "error"będzie dobrze. (2) teemoże dołączyć do wielu plików jednocześnie. (3) Dlaczego nie catzamiast tego grep "^"? (4) twój skrypt zawiedzie, gdy zaczyna się wyjście stderr _stdout_. (5) Dlaczego?
pLumo
@RoVo: pierwsze 2 skomentowane wiersze pokazują algorytm, prostszy niż przykład potwierdzenia koncepcji. 1): ls loopspowoduje to wyświetlenie zarówno wyjścia standardowego, jak i standardowego, mieszanego (alternatywnie), w kontrolowanej kolejności; tak, abyśmy mogli sprawdzić, czy zachowaliśmy porządkowanie standardowego standardowego standardu pomimo oznaczenia standardowego standardu 2): może gnu ogon, ale nie zwykły ogon (np. na aix.). 3): grep „^” pokazuje także obie nazwy plików. 4): można to zmienić za pomocą zmiennej. 5): zwinięty przykład działa na starych osesach (np. Stary aix), gdzie go przetestowałem (brak dostępnego perla).
Olivier Dulac
(1) wielokrotne echo do stderr i stout byłoby w porządku, ale w porządku, nieważne, byłoby po prostu łatwiejsze do odczytania. (3) zgadzają się, (4) jasne, ale zawiedzie, jeśli zacznie od wszystkiego, co zawiera zmienna. (5) Rozumiem.
pLumo
@RoVo Zgadzam się z twoim 1). dla 4), zmienna może być tak złożona, jak to konieczne, aby zniknąć pb (np .: uniquetag="banaNa11F453355B28E1158D4E516A2D3EDF96B3450406...)
Olivier Dulac
1
Oczywiście, nie jest to bardzo prawdopodobne, ale w każdym razie może to spowodować problem z bezpieczeństwem. A potem możesz usunąć ten ciąg przed wydrukowaniem do pliku ;-)
pLumo
2

Jeśli kolejność danych wyjściowych musi być: stdout, to stderr ; nie ma rozwiązania tylko z przekierowaniem.
Stderr musi być zapisany w pliku tymczasowym

cmd 2>>file-err | tee -a file1 >>file2
cat file-err >> file1
rm file-err

Opis:

Jedynym sposobem przekierowania jednego wyjścia (fd takiego jak stdout lub stderr) do dwóch plików jest odtworzenie go. Polecenie teejest właściwym narzędziem do odtwarzania zawartości deskryptora pliku. Tak więc początkowym pomysłem posiadania jednego wyjścia na dwóch plikach byłoby użycie:

... |  tee file1 file2

To odtwarza standardowe wejście tee do obu plików (1 i 2), pozostawiając wyjście tee wciąż nieużywane. Ale musimy dołączyć (użyć -a) i potrzebujemy tylko jednej kopii. To rozwiązuje oba problemy:

... | tee -a file1 >>file2

Aby dostarczyć teestdout (ten do powtórzenia), musimy użyć stderr bezpośrednio z komendy. Jednym ze sposobów, jeśli kolejność nie jest ważna (kolejność wyjścia zostanie (najprawdopodobniej) zachowana w stanie wygenerowanym, w zależności od tego, co nastąpi jako pierwsze, zostanie zapisane najpierw). Zarówno:

  1. cmd 2>>file1 | tee -a file2 >>file1
  2. cmd 2>>file1 > >( tee -a file2 >>file1 )
  3. ( cmd | tee -a file2 ) >> file1 2>&1

Opcja 2 działa tylko w niektórych powłokach. Opcja 3 korzysta z dodatkowej podpowłoki (wolniej), ale nazwy plików należy używać tylko raz.

Ale jeśli stdout musi być pierwszy (cokolwiek zostanie wygenerowane wyjście zamówienia), musimy zapisać stderr, aby dołączyć go do pliku na końcu (pierwsze opublikowane rozwiązanie).

Izaak
źródło
1
Lub przechowuj w pamięci jak sponge:(cmd | tee -a out >> out+err) 2>&1 | sponge >> out+err
Stéphane Chazelas
1

W interesie różnorodności:

Jeśli twój system obsługuje /dev/stderr, to

(cmd | tee -a /dev/stderr) 2>> file1 >> file2

będzie działać. Standardowe wyjście cmd jest wysyłane zarówno do standardowego, jak i standardowego potoku. Standardowy błąd cmdomija tee i wychodzi ze stderr rurociągu.

Więc

  • standardowe wyjście rurociągu to tylko standardowe wejście cmdi
  • stderr rurociągu to stdout i stderr z cmdzmieszanego.

W takim razie wystarczy wysłać te strumienie do odpowiednich plików.

Jak w przypadku prawie każdego takiego podejścia (w tym odpowiedzi Stéphane'a ), file1linie mogą być nieczynne.

Scott
źródło