Tambahkan baris terakhir stdin ke seluruh stdin

9

Pertimbangkan skrip ini:

tmpfile=$(mktemp)

cat <<EOS > "$tmpfile"
line 1
line 2
line 3
EOS

cat <(tail -1 "$tmpfile") "$tmpfile"

Ini berfungsi dan menghasilkan:

line 3
line 1
line 2
line 3

Katakanlah sumber input kami, alih-alih menjadi file aktual, malah stdin:

cat <<EOS | # what goes here now?
line 1
line 2
line 3
EOS

Bagaimana kita memodifikasi perintah:

cat <(tail -1 "$tmpfile") "$tmpfile"

Sehingga masih menghasilkan output yang sama, dalam konteks yang berbeda ini?

CATATAN: Heredoc spesifik yang saya lakukan, serta penggunaan Heredoc sendiri, hanyalah ilustrasi. Setiap jawaban yang dapat diterima harus berasumsi bahwa ia menerima data sewenang-wenang melalui stdin .

Jonah
sumber
1
stdin selalu merupakan "file aktual" (fifo / socket / etc adalah file juga; tidak semua file dapat dicari). Jawaban atas pertanyaan Anda adalah sepele "gunakan file sementara" atau horor yang akan memuat seluruh file dalam memori. "Bagaimana saya bisa mengambil data lama dari aliran tanpa menyimpannya di mana saja ?" tidak dapat memiliki jawaban yang bagus
Mosvy
1
@mosvy Itu jawaban yang bisa diterima jika Anda ingin menambahkannya.
Jonah
2
@mosvy Seperti yang dikatakan Jonah, jawaban harus diposting di kotak jawaban. Saya tahu itu sulit untuk membaca salah satu situs web saat ini, tapi tolong abaikan merah yang perlahan-lahan meneteskan penglihatan Anda dan gunakan textarea yang lebih rendah.
wizzwizz4

Jawaban:

7

Mencoba:

awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'

Contoh

Tentukan variabel dengan input kami:

$ input="line 1
> line 2
> line 3"

Jalankan perintah kami:

$ echo "$input" | awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'
line 3
line 1
line 2
line 3

Atau, tentu saja, kita bisa menggunakan di sini-doc:

$ cat <<EOS | awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'
line 1
line 2
line 3
EOS
line 3
line 1
line 2
line 3

Bagaimana itu bekerja

  • x=x $0 ORS

    Ini menambahkan setiap baris input ke variabel x.

    Dalam awk, ORSadalah pemisah catatan output . Secara default, ini adalah karakter baris baru.

  • END{printf "%s", $0 ORS x}

    Setelah kita membaca di seluruh file, ini akan mencetak baris terakhir $0,, diikuti oleh isi dari keseluruhan file x,.

Karena ini membaca seluruh input ke dalam memori, itu tidak akan sesuai untuk input besar ( misalnya gigabyte).

John1024
sumber
John terima kasih. Jadi apakah tidak mungkin untuk melakukan ini dengan cara yang analog dengan contoh file bernama saya di OP? Saya membayangkan stdin diduplikasi entah bagaimana ... semacam teeitu, tetapi dari stdin dan file, kita akan memipatkan stdin yang sama menjadi dua proses penggantian yang berbeda. atau apa pun yang kira-kira setara dengan itu?
Jonah
5

Jika stdin menunjuk ke file yang dapat dicari (seperti dalam kasus bash (tetapi tidak semua shell lainnya) di sini dokumen yang diimplementasikan dengan file temp), Anda bisa mendapatkan ekornya dan kemudian mencari kembali sebelum membaca konten lengkap:

mencari operator tersedia di zshatau ksh93shells, atau bahasa scripting seperti tcl / perl / python, tetapi tidak di bash. Tetapi Anda selalu dapat memanggil penerjemah yang lebih canggih dari itu bashjika Anda harus menggunakannya bash.

ksh93 -c 'tail -n1; cat <#((0))' <<...

Atau

zsh -c 'zmodload zsh/system; tail -n1; sysseek 0; cat' <<...

Sekarang, itu tidak akan berfungsi ketika stdin menunjuk ke file yang tidak dapat dicari seperti pipa atau soket. Kemudian, satu-satunya pilihan adalah membaca dan menyimpan (dalam memori atau dalam file sementara ...) seluruh input.

Beberapa solusi untuk menyimpan dalam memori telah diberikan.

Dengan tempfile, with zsh, Anda bisa melakukannya dengan:

seq 10 | zsh -c '{ cat =(sed \$w/dev/fd/3); } 3>&1'

Jika di Linux, dengan bashatau zshatau shell apa pun yang menggunakan file temp untuk dokumen di sini, Anda sebenarnya bisa menggunakan file temp yang dibuat oleh dokumen di sini untuk menyimpan output:

seq 10 | {
  chmod u+w /dev/fd/3 # only needed in bash5+
  cat > /dev/fd/3
  tail -n1 /dev/fd/3
  cat <&3
} 3<<EOF
EOF
Stéphane Chazelas
sumber
4
cat <<EOS | sed -ne '1{h;d;}' -e 'H;${G;p;}'
line 1
line 2
line 3
EOS

Masalah dengan menerjemahkan ini ke sesuatu yang menggunakan tailadalah yang tailperlu membaca seluruh file untuk menemukan akhir. Untuk menggunakannya dalam pipa Anda, Anda harus melakukannya

  1. Berikan seluruh isi dokumen kepada tail.
  2. Berikan lagi kepada cat.
  3. Dalam urutan itu.

Agak sulit bukan untuk menduplikasi konten dokumen ( teemelakukan itu) tetapi untuk mendapatkan hasil yang tailakan terjadi sebelum sisa dokumen dikeluarkan, tanpa menggunakan file sementara perantara.

Menggunakan sed(atau awk, seperti yang dilakukan John1024 ) menghilangkan penguraian ganda data dan masalah pemesanan dengan menyimpan data dalam memori.

The sedsolusi yang saya usulkan adalah untuk

  1. 1{h;d;}, simpan baris pertama di ruang tunggu, apa adanya, dan lewati ke baris berikutnya.
  2. H, tambahkan satu sama lain baris ke ruang penahanan dengan baris baru tertanam.
  3. ${G;p;}, tambahkan ruang penyimpanan ke baris terakhir dengan baris baru yang disematkan dan cetak data yang dihasilkan.

Ini adalah terjemahan harfiah dari solusi John1024 ke dalam sed, dengan peringatan bahwa standar POSIX hanya menjamin bahwa ruang penahanan setidaknya 8192 byte (8 KiB; tetapi merekomendasikan bahwa buffer ini dialokasikan secara dinamis dan diperluas sesuai kebutuhan, yang kedua GNU seddan BSD sedsedang melakukan).


Jika Anda membiarkan diri Anda menggunakan pipa bernama:

mkfifo mypipe
cat <<EOS | tee mypipe | cat <( tail -n 1 mypipe ) -
line 1
line 2
line 3
EOS
rm -f mypipe

Ini digunakan teeuntuk mengirim data ke bawah mypipedan pada saat yang sama cat. The catutilitas pertama akan membaca output dari tail(yang berbunyi dari mypipe, yang teemenulis untuk), dan kemudian menambahkan salinan dokumen yang datang langsung dari tee.

Ada kesalahan serius dalam hal ini, dalam hal jika dokumen terlalu besar (lebih besar dari ukuran buffer pipa), teetulisan ke mypipedan catakan memblokir sambil menunggu pipa (tanpa nama) kosong. Itu tidak akan dikosongkan sampai catdibaca darinya. cattidak akan membaca dari itu sampai tailselesai. Dan tailtidak akan selesai sampai teeselesai. Ini adalah situasi kebuntuan klasik.

Variasi

tee >( tail -n 1 >mypipe ) | cat mypipe -

memiliki masalah yang sama.

Kusalananda
sumber
2
Yang sedtidak berfungsi jika input hanya memiliki satu baris (mungkin sed '1h;1!H;$!d;G'). Perhatikan juga bahwa beberapa sedimplementasi memiliki batas rendah pada ukuran pola dan ruang penyimpanan.
Stéphane Chazelas
Solusi pipa bernama adalah jenis hal yang saya cari. Keterbatasan itu memalukan. Saya mengerti penjelasan Anda kecuali untuk "Dan ekor tidak akan selesai sampai tee selesai" - bisakah Anda menjelaskan mengapa itu terjadi?
Jonah
2

Ada alat yang disebutkan peedalam kumpulan utilitas baris perintah yang biasanya dikemas dengan nama "moreutils" (atau dapat diambil dari situs web asalnya ).

Jika Anda dapat memilikinya di sistem Anda, maka yang setara untuk contoh Anda akan seperti:

cat <<EOS | pee 'tail -1' cat 
line 1
line 2
line 3
EOS

Memesan perintah yang dijalankan peeadalah penting karena dieksekusi dalam urutan yang disediakan.

LL3
sumber
1

Mencoba:

cat <<EOS # | what goes here now? Nothing!
line 3
line 1
line 2
line 3
EOS

Karena semuanya adalah data literal ("dokumen yang ada di sini"), dan perbedaan antara itu dan output yang diinginkan adalah sepele, hanya memijat data literal di sana untuk mencocokkan dengan output.

Sekarang anggap line 3berasal dari suatu tempat dan disimpan dalam variabel yang disebut lastline:

cat <<EOS # | what goes here now? Nothing!
$lastline
line 1
line 2
$lastline
EOS

Dalam dokumen di sini, kita dapat menghasilkan teks dengan mengganti variabel. Tidak hanya itu tetapi kita dapat menghitung teks menggunakan substitusi perintah:

cat <<EOS
this is template text
here we have a hex conversion: $(printf "%x" 42)
EOS

Kami dapat menginterpolasi banyak baris:

cat <<EOS
multi line
preamble
$(for x in 3 1 2 3; do echo line $x ; done)
epilog
EOS

Secara umum, hindari pemrosesan teks templat dokumen di sini; cobalah untuk membuatnya menggunakan kode interpolasi.

Kaz
sumber
1
Jujur saya tidak tahu apakah ini lelucon atau tidak. Dalam cat <<EOS...OP itu hanya contoh standin untuk "capping file arbitrary," untuk membuat posting spesifik dan pertanyaan menjadi jelas. Apakah itu benar-benar tidak jelas bagi Anda, atau apakah Anda hanya berpikir akan pintar untuk menafsirkan pertanyaan itu secara harfiah?
Jonah
@Jonah Pertanyaannya dengan jelas mengatakan "[l] et mengatakan bahwa sumber input kami, daripada menjadi file yang sebenarnya, malah stdin:". Tidak ada tentang "file arbitrer"; ini tentang di sini dokumen. Dokumen di sini tidak sewenang-wenang. Ini bukan input ke program Anda, tetapi sepotong sintaksis yang dipilih oleh programmer.
Kaz
1
Saya pikir konteks dan jawaban yang ada memperjelas bahwa itulah masalahnya, jika hanya karena interpretasi Anda benar, Anda benar-benar harus berasumsi bahwa baik saya maupun poster lain yang menjawab menyadari bahwa mungkin untuk menyalin dan menempel baris kode. Namun demikian, saya akan mengedit pertanyaan untuk membuatnya eksplisit.
Jonah
1
Kaz, terima kasih atas jawabannya, tetapi perhatikan bahkan dengan hasil edit Anda, Anda kehilangan maksud pertanyaan. Anda menerima input multiline sembarang melalui pipa . Anda tidak tahu apa yang akan terjadi. Tugas Anda adalah menampilkan baris input terakhir, diikuti oleh seluruh input.
Jonah
1
Kaz, inputnya hanya ada sebagai contoh. Kebanyakan orang, termasuk saya, merasa terbantu memiliki contoh input nyata dan output yang diharapkan, bukan hanya pertanyaan abstrak. Anda adalah satu-satunya yang bingung dengan ini.
Jonah
0

Jika Anda tidak peduli dengan pesanan. Maka ini akan berhasil cat lines | tee >(tail -1). Seperti yang dikatakan orang lain. Anda perlu membaca file dua kali, atau buffer seluruh file, untuk melakukannya dalam urutan yang Anda minta.

ctrl-alt-delor
sumber