Bagaimana saya bisa mengirim stdout ke beberapa perintah?

186

Ada beberapa perintah yang memfilter atau bertindak pada input, dan kemudian meneruskannya sebagai output, saya pikir biasanya untuk stdout- tetapi beberapa perintah hanya akan mengambil stdindan melakukan apa pun yang mereka lakukan dengannya, dan tidak menghasilkan apa-apa.

Saya paling akrab dengan OS X dan jadi ada dua yang langsung terlintas di benak saya pbcopydan pbpaste- yang merupakan cara mengakses clipboard sistem.

Bagaimanapun, saya tahu bahwa jika saya ingin mengambil stdout dan meludahkan output untuk pergi ke keduanya stdoutdan file maka saya bisa menggunakan teeperintah. Dan saya tahu sedikit tentang itu xargs, tetapi saya tidak berpikir itu yang saya cari.

Saya ingin tahu bagaimana saya dapat membagi stdoutuntuk pergi antara dua (atau lebih) perintah. Sebagai contoh:

cat file.txt | stdout-split -c1 pbcopy -c2 grep -i errors

Mungkin ada contoh yang lebih baik daripada yang itu, tapi saya benar-benar tertarik mengetahui bagaimana saya bisa mengirim stdout ke perintah yang tidak menyampaikannya dan tetap menjaga agar stdouttidak "diredam" - Saya tidak bertanya tentang cara catmenyimpan file dan grepbagian dari itu dan salin ke clipboard - perintah spesifik tidak begitu penting.

Juga - saya tidak bertanya bagaimana mengirim ini ke file dan stdout- ini mungkin pertanyaan "duplikat" (maaf) tapi saya melihat beberapa dan hanya bisa menemukan yang serupa yang bertanya tentang bagaimana membagi antara stdout dan file - dan jawaban atas pertanyaan-pertanyaan itu tampaknya tee, yang saya pikir tidak akan berhasil untuk saya.

Akhirnya, Anda mungkin bertanya "mengapa tidak membuat pbcopy hal terakhir dalam rantai pipa?" dan tanggapan saya adalah 1) bagaimana jika saya ingin menggunakannya dan masih melihat output di konsol? 2) bagaimana jika saya ingin menggunakan dua perintah yang tidak keluar stdoutsetelah mereka memproses input?

Oh, dan satu hal lagi - saya sadar saya bisa menggunakan teedan bernama pipe ( mkfifo) tapi saya berharap cara ini bisa dilakukan secara inline, secara ringkas, tanpa pengaturan sebelumnya :)

cwd
sumber
Kemungkinan Gandakan dari unix.stackexchange.com/questions/26964/…
Nikhil Mulley

Jawaban:

240

Anda dapat menggunakan teedan memproses substitusi untuk ini:

cat file.txt | tee >(pbcopy) | grep errors

Ini akan mengirim semua output cat file.txtke pbcopy, dan Anda hanya akan mendapatkan hasilnya grepdi konsol Anda.

Anda dapat menempatkan beberapa proses di teebagian:

cat file.txt | tee >(pbcopy) >(do_stuff) >(do_more_stuff) | grep errors
Tikar
sumber
21
Bukan masalah dengan pbcopy, tetapi layak disebutkan secara umum: apa pun output proses substitusi juga dilihat oleh segmen pipa berikutnya, setelah input asli; misalnya: seq 3 | tee >(cat -n) | cat -e(memberi cat -nnomor pada garis input, cat -emenandai baris baru $; Anda akan melihat bahwa cat -editerapkan pada input asli (pertama) dan (kemudian) dari output cat -n). Output dari berbagai proses penggantian akan tiba dalam urutan non-deterministik.
mklement0
49
Satu- >(satunya yang berfungsi di bash. Jika Anda mencobanya menggunakan misalnya shitu tidak akan berhasil. Penting untuk membuat pemberitahuan ini.
AAlvz
10
@AAlvz: Poin bagus: substitusi proses bukan fitur POSIX; dash, yang bertindak seperti shdi Ubuntu, tidak mendukungnya, dan bahkan Bash sendiri menonaktifkan fitur ketika dipanggil sebagai shatau ketika set -o posixsedang berlaku. Namun, bukan hanya Bash yang mendukung proses penggantian: kshdan zshmendukung mereka juga (tidak yakin tentang orang lain).
mklement0
2
@ mklement0 yang tampaknya tidak benar. Pada zsh (Ubuntu 14.04), baris Anda mencetak: 1 1 2 2 3 3 1 $ 2 $ 3 $ Yang menyedihkan, karena saya benar-benar ingin fungsi seperti yang Anda katakan.
Aktau
2
@ Aktau: Memang, perintah sampel saya hanya berfungsi seperti yang dijelaskan dalam bashdan ksh- zshtampaknya tidak mengirim output dari penggantian proses keluaran melalui pipa (bisa dibilang, itu lebih disukai , karena tidak mencemari apa yang dikirim ke segmen pipa berikutnya - meskipun masih dicetak ). Dalam semua shell yang disebutkan, bagaimanapun, umumnya bukan ide yang baik untuk memiliki satu saluran pipa di mana output stdout reguler dan output dari substitusi proses dicampur - pemesanan output tidak akan dapat diprediksi, dengan cara yang mungkin hanya permukaan jarang atau dengan besar set data keluaran.
mklement0
124

Anda dapat menentukan beberapa nama file tee, dan selain itu output standar dapat disalurkan ke dalam satu perintah. Untuk mengirim output ke beberapa perintah, Anda perlu membuat beberapa pipa dan menentukan masing-masing sebagai satu output tee. Ada beberapa cara untuk melakukan ini.

Substitusi proses

Jika shell Anda adalah ksh93, bash atau zsh, Anda dapat menggunakan subtitusi proses. Ini adalah cara untuk melewatkan pipa ke perintah yang mengharapkan nama file. Shell membuat pipa dan memberikan nama file seperti /dev/fd/3ke perintah. Nomornya adalah deskriptor file yang terhubung dengan pipa. Beberapa varian unix tidak mendukung /dev/fd; pada ini, pipa bernama digunakan sebagai gantinya (lihat di bawah).

tee >(command1) >(command2) | command3

Deskriptor file

Di setiap shell POSIX, Anda dapat menggunakan banyak deskriptor file secara eksplisit. Ini memerlukan varian unix yang mendukung /dev/fd, karena semua kecuali satu dari output teeharus ditentukan oleh nama.

{ { { tee /dev/fd/3 /dev/fd/4 | command1 >&9;
    } 3>&1 | command2 >&9;
  } 4>&1 | command3 >&9;
} 9>&1

Bernama pipa

Metode paling dasar dan portabel adalah dengan menggunakan pipa bernama . Kelemahannya adalah Anda perlu menemukan direktori yang bisa ditulis, membuat pipa, dan membersihkannya setelah itu.

tmp_dir=$(mktemp -d)
mkfifo "$tmp_dir/f1" "$tmp_dir/f2"
command1 <"$tmp_dir/f1" & pid1=$!
command2 <"$tmp_dir/f2" & pid2=$!
tee "$tmp_dir/f1" "$tmp_dir/f2" | command3
rm -rf "$tmp_dir"
wait $pid1 $pid2
Gilles
sumber
10
Terima kasih banyak telah menyediakan dua versi alternatif untuk mereka yang tidak ingin bergantung pada bash atau ksh tertentu.
trr
tee "$tmp_dir/f1" "$tmp_dir/f2" | command3harus pasti command3 | tee "$tmp_dir/f1" "$tmp_dir/f2", karena Anda ingin stdout dari command3pipa tee, bukan? Saya menguji versi Anda di bawah dashdan teememblokir menunggu input tanpa batas, tetapi beralih urutan menghasilkan hasil yang diharapkan.
Adrian Günter
1
@ AdrianGünter No. Ketiga contoh membaca data dari input standar dan mengirimkannya ke masing-masing command, command2dan command3.
Gilles
@Gilles begitu, saya salah mengartikan maksud dan mencoba menggunakan cuplikan secara salah. Terimakasih atas klarifikasinya!
Adrian Günter
Jika Anda tidak memiliki kontrol pada shell yang digunakan, tetapi Anda dapat menggunakan bash secara eksplisit, Anda dapat melakukannya <command> | bash -c 'tee >(command1) >(command2) | command3'. Ini membantu dalam kasus saya.
gc5
16

Cukup mainkan dengan proses substitusi.

mycommand_exec |tee >(grep ook > ook.txt) >(grep eek > eek.txt)

grepadalah dua binari yang memiliki output yang sama dari mycommand_execinput spesifik prosesnya.

Nikhil Mulley
sumber
16

Jika Anda menggunakan zshmaka Anda dapat memanfaatkan kekuatan MULTIOSfitur, yaitu menyingkirkan teeperintah sepenuhnya:

uname >file1 >file2

hanya akan menulis output unameke dua file berbeda: file1dan file2, apa yang setara denganuname | tee file1 >file2

Demikian pula pengalihan input standar

wc -l <file1 <file2

setara dengan cat file1 file2 | wc -l(harap dicatat bahwa ini tidak sama dengan wc -l file1 file2, yang kemudian menghitung jumlah baris dalam setiap file secara terpisah).

Tentu saja Anda juga dapat menggunakan MULTIOSuntuk mengarahkan kembali output bukan ke file tetapi ke proses lain, menggunakan substitusi proses, misalnya:

echo abc > >(grep -o a) > >(tr b x) > >(sed 's/c/y/')
jimmij
sumber
3
Senang mendengarnya. MULTIOSadalah opsi yang AKTIF secara default (dan dapat dimatikan dengan unsetopt MULTIOS).
mklement0
6

Untuk output yang cukup kecil yang dihasilkan oleh suatu perintah, kita dapat mengalihkan output ke file sementara, dan mengirimkan file-file sementara ke perintah dalam loop. Ini bisa berguna ketika urutan perintah yang dieksekusi mungkin penting.

Skrip berikut, misalnya, dapat melakukannya:

#!/bin/sh

temp=$( mktemp )
cat /dev/stdin > "$temp"

for arg
do
    eval "$arg" < "$temp"
done
rm "$temp"

Uji berjalan pada Ubuntu 16.04 dengan /bin/shsebagai dashshell:

$ cat /etc/passwd | ./multiple_pipes.sh  'wc -l'  'grep "root"'                                                          
48
root:x:0:0:root:/root:/bin/bash
Sergiy Kolodyazhnyy
sumber
5

Abadikan perintah STDOUTke variabel dan gunakan kembali sebanyak yang Anda suka:

commandoutput="$(command-to-run)"
echo "$commandoutput" | grep -i errors
echo "$commandoutput" | pbcopy

Jika Anda perlu menangkap STDERRjuga, gunakan 2>&1di akhir perintah, seperti:

commandoutput="$(command-to-run 2>&1)"
laebshade
sumber
3
Di mana variabel disimpan? Jika Anda berurusan dengan file besar atau semacamnya, bukankah ini akan memakan banyak memori? Apakah ukuran variabel terbatas?
cwd
1
bagaimana jika $ commandoutput sangat besar ?, lebih baik menggunakan pipa dan memproses substitusi.
Nikhil Mulley
4
Jelas solusi ini hanya mungkin bila Anda tahu ukuran output akan dengan mudah masuk ke memori, dan Anda OK dengan buffering seluruh output sebelum menjalankan perintah selanjutnya di atasnya. Pipa mengatasi dua masalah ini dengan memungkinkan data panjang sewenang-wenang dan mengalirkannya secara real time ke penerima saat itu dihasilkan.
trr
2
Ini adalah solusi yang baik jika Anda memiliki output kecil, dan Anda tahu bahwa output akan berupa teks dan bukan biner. (variabel shell sering tidak biner aman)
Rucent88
1
Saya tidak bisa menjalankan ini dengan data biner. Saya pikir itu sesuatu dengan gema mencoba menafsirkan byte nol atau data noncharacter lainnya.
Rolf
0

Berikut adalah solusi parsial cepat dan kotor, kompatibel dengan semua shell termasuk busybox.

Masalah yang lebih sempit yang dipecahkannya adalah: mencetak komplet stdoutke satu konsol, dan memfilternya di konsol lain, tanpa file sementara atau pipa bernama.

  • Mulai sesi lain ke host yang sama. Untuk mengetahui nama TTY-nya, ketikkan tty. Mari kita asumsikan /dev/pty/2.
  • Di sesi pertama, jalankan the_program | tee /dev/pty/2 | grep ImportantLog:

Anda mendapatkan satu log lengkap, dan satu yang difilter.

Victor Sergienko
sumber