Baca nilai ke dalam variabel shell dari pipa


205

Saya mencoba untuk mendapatkan bash untuk memproses data dari stdin yang disalurkan ke, tetapi tidak berhasil. Yang saya maksud adalah tidak ada pekerjaan berikut:

echo "hello world" | test=($(< /dev/stdin)); echo test=$test
test=

echo "hello world" | read test; echo test=$test
test=

echo "hello world" | test=`cat`; echo test=$test
test=

di mana saya ingin hasilnya test=hello world. Saya sudah mencoba menempatkan "" tanda kutip di sekitar "$test"yang tidak berhasil.


1
Contoh Anda .. echo "hello world" | membaca tes; echo test = $ test bekerja dengan baik untuk saya .. hasil: test = hello world; lingkungan apa yang menjalankan ini di bawah? Saya menggunakan bash 4.2 ..
alex.pilon

Apakah Anda ingin beberapa baris dalam satu kali baca? Contoh Anda hanya memperlihatkan satu baris, tetapi deskripsi masalahnya tidak jelas.
Charles Duffy

2
@ alex.pilon, saya menjalankan Bash versi 4.2.25, dan contohnya tidak bekerja untuk saya juga. Mungkin itu masalah pilihan runtime Bash atau variabel lingkungan? Saya punya contoh tidak bekerja dengan Sh juga, jadi mungkin Bash dapat mencoba agar kompatibel dengan Sh?
Hibou57

2
@ Hibou57 - Saya mencoba ini lagi di bash 4.3.25 dan tidak lagi berfungsi. Ingatan saya tidak jelas tentang hal ini dan saya tidak yakin apa yang telah saya lakukan untuk membuatnya bekerja.
alex.pilon

2
@ Hibou57 @ alex.pilon cmd terakhir dalam pipa harus memengaruhi vars di bash4> = 4.2 dengan shopt -s lastpipe- tldp.org/LDP/abs/html/bashver4.html#LASTPIPEOPT
imz - Ivan Zakharyaschev

Jawaban:


162

Menggunakan

IFS= read var << EOF
$(foo)
EOF

Anda bisa mengelabui readpenerimaan dari pipa seperti ini:

echo "hello world" | { read test; echo test=$test; }

atau bahkan menulis fungsi seperti ini:

read_from_pipe() { read "$@" <&0; }

Tapi tidak ada gunanya - tugas variabel Anda mungkin tidak bertahan! Sebuah pipa dapat menelurkan subkulit, di mana lingkungan diwarisi oleh nilai, bukan dengan referensi. Ini sebabnyaread tidak repot dengan input dari pipa - itu tidak terdefinisi.

FYI, http://www.etalabs.net/sh_tricks.html adalah koleksi bagus dari cruft yang diperlukan untuk melawan keanehan dan ketidakcocokan shell bourne, sh.


Anda dapat menjadikan tugas terakhir dengan melakukan ini sebagai gantinya: `test =` `echo" hello world "| {baca tes; echo $ test; } `` `
Compholio

2
Mari kita coba ini lagi (ternyata lolos backticks di markup ini menyenangkan):test=`echo "hello world" | { read test; echo $test; }`
Compholio

dapatkah saya bertanya mengapa Anda menggunakan {}alih-alih ()mengelompokkan dua perintah itu?
Jürgen Paul

4
Triknya bukan dalam readmengambil input dari pipa, tetapi menggunakan variabel dalam shell yang sama yang mengeksekusi read.
chepner

1
Dapat bash permission deniedketika mencoba menggunakan solusi ini. Kasus saya sangat berbeda, tapi aku tidak bisa menemukan jawaban untuk itu di mana saja, apa yang bekerja untuk saya adalah (contoh yang berbeda namun penggunaan serupa): pip install -U echo $(ls -t *.py | head -1). Dalam hal, seseorang pernah memiliki masalah yang sama dan menemukan jawaban ini seperti saya.
ivan_bilan

111

jika Anda ingin membaca banyak data dan mengerjakan setiap baris secara terpisah, Anda dapat menggunakan sesuatu seperti ini:

cat myFile | while read x ; do echo $x ; done

jika Anda ingin membagi baris menjadi beberapa kata, Anda dapat menggunakan beberapa variabel sebagai ganti x seperti ini:

cat myFile | while read x y ; do echo $y $x ; done

kalau tidak:

while read x y ; do echo $y $x ; done < myFile

Tetapi segera setelah Anda mulai ingin melakukan sesuatu yang benar-benar pandai dengan hal-hal semacam ini, Anda lebih baik menggunakan bahasa scripting seperti perl di mana Anda dapat mencoba sesuatu seperti ini:

perl -ane 'print "$F[0]\n"' < myFile

Ada kurva belajar yang cukup curam dengan perl (atau saya kira salah satu dari bahasa-bahasa ini) tetapi Anda akan merasa jauh lebih mudah dalam jangka panjang jika Anda ingin melakukan apa pun kecuali skrip yang paling sederhana. Saya akan merekomendasikan Perl Cookbook dan, tentu saja, The Perl Programming Language oleh Larry Wall et al.


8
"Sebagai alternatif" adalah cara yang benar. Tidak ada UUoC dan tidak ada subkulit. Lihat BashFAQ / 024 .
Dijeda sampai pemberitahuan lebih lanjut.

43

readtidak akan membaca dari pipa (atau mungkin hasilnya hilang karena pipa membuat subkulit). Namun, Anda dapat menggunakan string di sini di Bash:

$ read a b c <<< $(echo 1 2 3)
$ echo $a $b $c
1 2 3

Tetapi lihat jawaban @ chepner untuk informasi tentang lastpipe.


Satu garis yang bagus dan sederhana, mudah dimengerti. Jawaban ini perlu lebih banyak upvotes.
David Parks

42

Ini adalah pilihan lain

$ read test < <(echo hello world)

$ echo $test
hello world

24
Keuntungan signifikan yang <(..)telah berakhir $(..)adalah <(..)mengembalikan setiap baris ke pemanggil segera setelah perintah yang dijalankan membuatnya tersedia. $(..)Namun, menunggu perintah untuk menyelesaikan dan menghasilkan semua outputnya sebelum membuat output tersedia untuk pemanggil.
Derek Mahar

37

Saya bukan ahli dalam Bash, tapi saya ingin tahu mengapa ini belum diusulkan:

stdin=$(cat)

echo "$stdin"

Satu bukti liner yang berfungsi untuk saya:

$ fortune | eval 'stdin=$(cat); echo "$stdin"'

4
Itu mungkin karena "membaca" adalah perintah bash, dan cat adalah biner terpisah yang akan diluncurkan dalam suatu subproses, sehingga kurang efisien.
dj_segfault

10
kadang-kadang kesederhanaan dan kejernihan efisiensi trump :)
Rondo

5
pasti jawaban yang paling mudah
drwatsoncode

Masalah saya berlari dengan ini adalah bahwa jika pipa tidak digunakan, skrip hang.
Dale Anderson

@ DaleA, tentu saja. Setiap program yang mencoba membaca dari input standar akan "hang" jika tidak ada yang dimasukkan ke dalamnya.
djanowski

27

bash4.2 memperkenalkan lastpipeopsi, yang memungkinkan kode Anda bekerja seperti yang tertulis, dengan mengeksekusi perintah terakhir dalam pipeline di shell saat ini, bukan subshell.

shopt -s lastpipe
echo "hello world" | read test; echo test=$test

2
ah! sangat bagus, ini. jika pengujian dalam shell interaktif, juga: "set + m" (tidak diperlukan dalam skrip .sh)
XXL

14

Sintaks untuk pipa implisit dari perintah shell ke variabel bash adalah

var=$(command)

atau

var=`command`

Dalam contoh Anda, Anda mengirim data ke pernyataan tugas, yang tidak mengharapkan input apa pun.


4
Karena $ () dapat disarangkan dengan mudah. Pikirkan di JAVA_DIR = $ (dirname $ (readlink -f $ (yang java))), dan coba dengan `. Anda harus melarikan diri tiga kali!
Albfan

8

Upaya pertama cukup dekat. Variasi ini seharusnya bekerja:

echo "hello world" | { test=$(< /dev/stdin); echo "test=$test"; };

dan hasilnya adalah:

test = halo dunia

Anda perlu kawat gigi setelah pipa untuk melampirkan tugas untuk menguji dan gema.

Tanpa kawat gigi, tugas untuk menguji (setelah pipa) dalam satu shell, dan gema "test = $ test" berada di shell yang terpisah yang tidak tahu tentang tugas itu. Itu sebabnya Anda mendapatkan "test =" di output daripada "test = hello world".


8

Di mata saya cara terbaik untuk membaca dari stdin di bash adalah yang berikut, yang juga memungkinkan Anda bekerja pada baris sebelum input berakhir:

while read LINE; do
    echo $LINE
done < /dev/stdin

1
Saya hampir menjadi gila sebelum menemukan ini. Terima kasih banyak sudah berbagi!
Samy Dindane

6

Karena aku jatuh cinta padanya, aku ingin menjatuhkan catatan. Saya menemukan utas ini, karena saya harus menulis ulang skrip sh lama agar kompatibel dengan POSIX. Ini pada dasarnya berarti untuk menghindari masalah pipa / subkulit yang diperkenalkan oleh POSIX dengan menulis ulang kode seperti ini:

some_command | read a b c

ke:

read a b c << EOF
$(some_command)
EOF

Dan kode seperti ini:

some_command |
while read a b c; do
    # something
done

ke:

while read a b c; do
    # something
done << EOF
$(some_command)
EOF

Tetapi yang terakhir tidak berlaku sama pada input kosong. Dengan notasi lama loop sementara tidak dimasukkan pada input kosong, tetapi dalam notasi POSIX itu! Saya pikir itu karena baris baru sebelum EOF, yang tidak dapat dibatalkan. Kode POSIX yang berperilaku lebih seperti notasi lama terlihat seperti ini:

while read a b c; do
    case $a in ("") break; esac
    # something
done << EOF
$(some_command)
EOF

Dalam kebanyakan kasus ini harus cukup baik. Namun sayangnya ini masih berperilaku tidak persis seperti notasi lama jika some_command mencetak baris kosong. Dalam notasi lama tubuh sementara dieksekusi dan dalam notasi POSIX kita istirahat di depan tubuh.

Pendekatan untuk memperbaiki ini mungkin terlihat seperti ini:

while read a b c; do
    case $a in ("something_guaranteed_not_to_be_printed_by_some_command") break; esac
    # something
done << EOF
$(some_command)
echo "something_guaranteed_not_to_be_printed_by_some_command"
EOF

5

Skrip cerdas yang dapat membaca data dari argumen PIPE dan perintah:

#!/bin/bash
if [[ -p /proc/self/fd/0 ]]
    then
    PIPE=$(cat -)
    echo "PIPE=$PIPE"
fi
echo "ARGS=$@"

Keluaran:

$ bash test arg1 arg2
ARGS=arg1 arg2

$ echo pipe_data1 | bash test arg1 arg2
PIPE=pipe_data1
ARGS=arg1 arg2

Penjelasan: Ketika sebuah skrip menerima data apa pun melalui pipa, maka stdin / proc / self / fd / 0 akan menjadi symlink ke sebuah pipa.

/proc/self/fd/0 -> pipe:[155938]

Jika tidak, itu akan mengarah ke terminal saat ini:

/proc/self/fd/0 -> /dev/pts/5

[[ -pOpsi bash dapat memeriksanya apakah itu pipa atau bukan.

cat -membaca dari stdin.

Jika kita gunakan cat -ketika tidak ada stdin, itu akan menunggu selamanya, itu sebabnya kita memasukkannya ke dalam ifkondisi.


Anda juga dapat menggunakan /dev/stdintautan ke/proc/self/fd/0
Elie G.

3

Memipiskan sesuatu ke ekspresi yang melibatkan tugas tidak berlaku seperti itu.

Sebagai gantinya, cobalah:

test=$(echo "hello world"); echo test=$test

2

Kode berikut:

echo "hello world" | ( test=($(< /dev/stdin)); echo test=$test )

akan bekerja juga, tetapi itu akan membuka sub-shell baru setelah pipa, di mana

echo "hello world" | { test=($(< /dev/stdin)); echo test=$test; }

biasa.


Saya harus menonaktifkan kontrol pekerjaan untuk menggunakan metode chepnars (saya menjalankan perintah ini dari terminal):

set +m;shopt -s lastpipe
echo "hello world" | read test; echo test=$test
echo "hello world" | test="$(</dev/stdin)"; echo test=$test

Bash Manual mengatakan :

pipa terakhir

Jika diatur, dan kontrol pekerjaan tidak aktif , shell menjalankan perintah terakhir dari pipeline yang tidak dieksekusi di latar belakang di lingkungan shell saat ini.

Catatan: kontrol pekerjaan dimatikan secara default di shell non-interaktif dan karenanya Anda tidak memerlukan set +mskrip di dalamnya.


1

Saya pikir Anda mencoba untuk menulis skrip shell yang dapat mengambil input dari stdin. tetapi ketika Anda mencoba untuk melakukannya inline, Anda tersesat mencoba membuat tes itu = variabel. Saya pikir itu tidak masuk akal untuk melakukannya sebaris, dan itu sebabnya tidak bekerja seperti yang Anda harapkan.

Saya berusaha mengurangi

$( ... | head -n $X | tail -n 1 )

untuk mendapatkan jalur tertentu dari berbagai input. jadi saya bisa mengetik ...

cat program_file.c | line 34

jadi saya perlu program shell kecil yang bisa membaca dari stdin. seperti kamu.

22:14 ~ $ cat ~/bin/line 
#!/bin/sh

if [ $# -ne 1 ]; then echo enter a line number to display; exit; fi
cat | head -n $1 | tail -n 1
22:16 ~ $ 

ini dia.


-1

Bagaimana dengan ini:

echo "hello world" | echo test=$(cat)

Pada dasarnya sebuah penipuan dari jawaban saya di bawah ini.
djanowski

Mungkin, saya berpendapat bahwa milik saya sedikit lebih bersih dan lebih dekat ke kode yang diposting di pertanyaan awal.
bingles
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.