อ่านค่าลงในตัวแปรเชลล์จากไพพ์


205

ฉันพยายามรับทุบตีเพื่อประมวลผลข้อมูลจาก stdin ที่ได้รับการประปา แต่ไม่โชคดี สิ่งที่ฉันหมายถึงคือไม่มีงานต่อไปนี้:

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=

test=hello worldที่ฉันต้องการที่จะออก ฉันได้ลองใส่เครื่องหมายคำพูดรอบ ๆ"$test"ที่ไม่ได้ผล


1
ตัวอย่างของคุณ .. echo "hello world" | อ่านทดสอบ echo test = $ test ทำงานได้ดีสำหรับฉัน .. ผลลัพธ์: test = hello world; สภาพแวดล้อมแบบใดที่ใช้สิ่งนี้ภายใต้ ฉันใช้ bash 4.2 ..
alex.pilon

คุณต้องการอ่านหลายบรรทัดในการอ่านครั้งเดียวหรือไม่? ตัวอย่างของคุณแสดงเพียงหนึ่งบรรทัด แต่คำอธิบายปัญหาไม่ชัดเจน
Charles Duffy

2
@ alex.pilon ฉันใช้ Bash เวอร์ชัน 4.2.25 และตัวอย่างของเขาก็ใช้ไม่ได้สำหรับฉันเช่นกัน อาจเป็นเรื่องของตัวเลือกรันไทม์ Bash หรือตัวแปรสภาพแวดล้อมหรือไม่? ฉันเป็นตัวอย่างที่ไม่ทำงานกับ Sh ทั้งสองดังนั้น Bash อาจจะพยายามเข้ากันได้กับ Sh หรือไม่?
Hibou57

2
@ Hibou57 - ฉันลองอีกครั้งในทุบตี 4.3.25 และมันไม่ทำงานอีกต่อไป ความทรงจำของฉันคลุมเครือในเรื่องนี้และฉันไม่แน่ใจว่าฉันควรทำอย่างไรเพื่อให้มันใช้งานได้
alex.pilon

2
@ Hibou57 @ alex.pilon cmd สุดท้ายในท่อควรมีผลกับ vars ใน bash4> = 4.2 ด้วยshopt -s lastpipe- tldp.org/LDP/abs/html/bashver4.html#LASTPIPEOPT
imz - Ivan Zakharyaschev

คำตอบ:


162

ใช้

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

คุณสามารถหลอกreadให้ยอมรับจากไปป์แบบนี้:

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

หรือแม้แต่เขียนฟังก์ชั่นเช่นนี้:

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

แต่ไม่มีประเด็น - การมอบหมายตัวแปรของคุณอาจไม่คงอยู่! ไพพ์ไลน์อาจวางไข่เชลล์ย่อยซึ่งสภาพแวดล้อมได้รับการสืบทอดโดยค่าไม่ใช่การอ้างอิง นี่คือเหตุผลที่readไม่ต้องกังวลกับอินพุตจากไปป์ - มันไม่ได้กำหนด

FYI, http://www.etalabs.net/sh_tricks.htmlเป็นคอลเลกชันที่ดีของ cruft ที่จำเป็นในการต่อสู้กับสิ่งแปลกประหลาดและความไม่ลงรอยกันของ bourne shell, sh.


คุณสามารถมอบหมายให้ทำงานล่าสุดโดยทำสิ่งนี้แทน: `test =` `echo" hello world "| {อ่านการทดสอบ; echo $ test; } `` `
Compholio

2
ลองใหม่อีกครั้ง (เห็นได้ชัดว่าการหลบหนี backticks ในมาร์กอัปนี้สนุก):test=`echo "hello world" | { read test; echo $test; }`
Compholio

ฉันจะถามได้ไหมว่าทำไมคุณถึงใช้{}แทนที่จะ()จัดกลุ่มคำสั่งทั้งสองนี้?
Jürgen Paul

4
เคล็ดลับคือไม่ได้อยู่ในการทำให้readการป้อนข้อมูลใช้จากท่อ readแต่ในการใช้ตัวแปรในเปลือกเดียวกันที่ดำเนินการ
chepner

1
เตรียมพร้อมbash permission deniedเมื่อพยายามใช้โซลูชันนี้ กรณีของฉันแตกต่างกันมาก แต่ฉันไม่สามารถหาคำตอบสำหรับมันได้ทุกที่ทุกสิ่งที่ทำงานสำหรับฉันคือ (เป็นตัวอย่างที่แตกต่างกัน pip install -U echo $(ls -t *.py | head -1)แต่การใช้งานที่คล้ายกัน): ในกรณีที่ใครบางคนเคยมีปัญหาที่คล้ายกันและสะดุดกับคำตอบนี้เช่นฉัน
ivan_bilan

111

หากคุณต้องการอ่านข้อมูลจำนวนมากและทำงานในแต่ละบรรทัดแยกกันคุณสามารถใช้สิ่งนี้:

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

หากคุณต้องการแบ่งบรรทัดออกเป็นหลายคำคุณสามารถใช้ตัวแปรหลายตัวแทน x ดังนี้:

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

อีกทางเลือกหนึ่ง:

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

แต่ทันทีที่คุณเริ่มที่จะต้องการทำสิ่งที่ฉลาดจริง ๆ กับสิ่งนี้คุณควรที่จะใช้ภาษาสคริปต์เช่น perl ที่คุณสามารถลองทำสิ่งนี้ได้ดีกว่า:

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

มีเส้นโค้งการเรียนรู้ที่ค่อนข้างสูงด้วย perl (หรือฉันเดาภาษาใด ๆ เหล่านี้) แต่คุณจะพบว่ามันง่ายขึ้นมากในระยะยาวหากคุณต้องการทำอะไรนอกจากสคริปต์ที่ง่ายที่สุด ฉันขอแนะนำ Perl Cookbook และแน่นอนภาษา Perl การเขียนโปรแกรมโดย Larry Wall และคณะ


8
"อีกทางหนึ่ง" เป็นวิธีที่ถูกต้อง ไม่มี UUoC และไม่มีการแบ่งย่อย ดูBashFAQ / 024
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

43

readจะไม่อ่านจากไปป์ (หรืออาจเป็นไปได้ว่าผลลัพธ์จะหายไปเนื่องจากไปป์สร้างเชลล์ย่อย) อย่างไรก็ตามคุณสามารถใช้สตริงที่นี่ใน Bash:

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

แต่เห็นคำตอบ @ chepner lastpipeสำหรับข้อมูลเกี่ยวกับ


ดีและเรียบง่ายหนึ่งซับง่ายต่อการเข้าใจ คำตอบนี้ต้องการ upvotes เพิ่มเติม
David Parks

42

นี่เป็นอีกทางเลือกหนึ่ง

$ read test < <(echo hello world)

$ echo $test
hello world

24
ข้อได้เปรียบที่สำคัญที่<(..)มีมากกว่า$(..)คือการ<(..)ส่งคืนแต่ละบรรทัดไปยังผู้เรียกทันทีที่คำสั่งที่ดำเนินการทำให้พร้อมใช้งาน $(..)อย่างไรก็ตามรอให้คำสั่งดำเนินการจนเสร็จสิ้นและสร้างเอาต์พุตทั้งหมดก่อนที่จะทำให้เอาต์พุตใด ๆ พร้อมใช้งานสำหรับผู้เรียก
Derek Mahar

37

ฉันไม่มีความเชี่ยวชาญใน Bash แต่ฉันสงสัยว่าทำไมสิ่งนี้จึงไม่ถูกเสนอ:

stdin=$(cat)

echo "$stdin"

หลักฐานหนึ่งซับที่เหมาะกับฉัน:

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

4
อาจเป็นเพราะ "read" เป็นคำสั่ง bash และ cat เป็นไบนารีแยกต่างหากที่จะเปิดตัวในกระบวนการย่อยดังนั้นจึงมีประสิทธิภาพน้อยกว่า
dj_segfault

10
บางครั้งความเรียบง่ายและประสิทธิภาพของทรัมป์ชัดเจน :)
Rondo

5
คำตอบที่ตรงไปตรงมาที่สุดแน่นอน
drwatsoncode

ปัญหาที่ฉันพบในกรณีนี้คือหากไม่ได้ใช้ไพพ์สคริปต์จะหยุดทำงาน
Dale Anderson

@DaleA แน่นอน โปรแกรมใด ๆ ที่พยายามอ่านจากอินพุตมาตรฐานจะ "หยุด" หากไม่มีการป้อนเข้า
djanowski

27

bash4.2 แนะนำlastpipeตัวเลือกซึ่งช่วยให้โค้ดของคุณทำงานตามที่เขียนไว้โดยดำเนินการคำสั่งสุดท้ายในไพพ์ไลน์ในเชลล์ปัจจุบันแทนที่จะเป็นเชลล์ย่อย

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

2
อา! ดีมากเลย หากการทดสอบในเชลล์แบบโต้ตอบเช่นกัน: "set + m" (ไม่จำเป็นในสคริปต์. sh)
XXL

14

ไวยากรณ์สำหรับไพพ์โดยนัยจากคำสั่ง shell ไปยังตัวแปร bash คือ

var=$(command)

หรือ

var=`command`

ในตัวอย่างของคุณคุณกำลังไพพ์ข้อมูลไปยังคำสั่งการกำหนดซึ่งไม่ได้คาดหวังอินพุตใด ๆ


4
เพราะ $ () สามารถซ้อนกันได้ง่าย คิดใน JAVA_DIR = $ (dirname $ (readlink -f $ (ซึ่ง java)) และลองใช้ด้วย ` คุณจะต้องหลบหนีสามครั้ง!
albfan

8

ความพยายามครั้งแรกใกล้เคียงกันมาก รูปแบบนี้ควรใช้งานได้:

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

และผลลัพธ์คือ:

ทดสอบ = สวัสดีชาวโลก

คุณต้องจัดฟันหลังไพพ์เพื่อใส่การมอบหมายเพื่อทดสอบและเสียงสะท้อน

หากไม่มีเครื่องหมายปีกกาการมอบหมายให้ทดสอบ (หลังจากไพพ์) อยู่ในเชลล์เดียวและ echo "test = $ test" อยู่ในเชลล์แยกต่างหากซึ่งไม่ทราบเกี่ยวกับการกำหนดนั้น นั่นเป็นเหตุผลที่คุณได้รับ "test =" ในผลลัพธ์แทนที่จะเป็น "test = hello world"


8

ในสายตาของฉันวิธีที่ดีที่สุดในการอ่านจาก stdin in bash คือวิธีต่อไปนี้ซึ่งช่วยให้คุณทำงานบนบรรทัดก่อนที่อินพุตจะสิ้นสุด:

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

1
ฉันเกือบคลั่งไปก่อนที่จะพบสิ่งนี้ ขอบคุณมากสำหรับการแบ่งปัน!
Samy Dindane

6

เพราะฉันตกหลุมรักฉันจึงอยากจะส่งโน้ต ฉันพบกระทู้นี้เพราะฉันต้องเขียนสคริปต์ sh เก่าเพื่อให้รองรับ POSIX นี่หมายถึงการหลีกเลี่ยงปัญหาท่อ / subshell ที่แนะนำโดย POSIX โดยการเขียนรหัสใหม่เช่นนี้:

some_command | read a b c

เป็น:

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

และรหัสเช่นนี้:

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

เป็น:

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

แต่หลังไม่ทำงานเหมือนกันในการป้อนข้อมูลที่ว่างเปล่า ด้วยสัญกรณ์เก่าขณะที่ลูปไม่ได้ถูกป้อนในอินพุตว่างเปล่า แต่ในเครื่องหมาย POSIX มันคือ! ฉันคิดว่าเป็นเพราะการขึ้นบรรทัดใหม่ก่อน EOF ซึ่งไม่สามารถแก้ไขได้ รหัส POSIX ซึ่งทำงานเหมือนสัญลักษณ์เก่า ๆ จะมีลักษณะดังนี้:

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

ในกรณีส่วนใหญ่ควรจะดีพอ แต่น่าเสียดายที่นี่ยังคงทำงานไม่เหมือนกับสัญกรณ์เก่าถ้า some_command พิมพ์บรรทัดว่างเปล่า ในสัญกรณ์เก่าขณะที่ร่างกายถูกประหารชีวิตและในสัญกรณ์ POSIX เราจะแตกต่อหน้าร่างกาย

วิธีแก้ไขปัญหานี้อาจมีลักษณะเช่นนี้:

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

สคริปต์อัจฉริยะที่สามารถอ่านข้อมูลจาก PIPE และอาร์กิวเมนต์บรรทัดคำสั่ง:

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

เอาท์พุท:

$ bash test arg1 arg2
ARGS=arg1 arg2

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

คำอธิบาย:เมื่อสคริปต์ได้รับข้อมูลใด ๆ ผ่านทางไพพ์ดังนั้น stdin / proc / self / fd / 0 จะเป็น symlink ไปยังไพพ์

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

ถ้าไม่มันจะชี้ไปที่เทอร์มินัลปัจจุบัน:

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

[[ -pตัวเลือกทุบตีสามารถตรวจสอบได้ว่ามันเป็นท่อหรือไม่

cat - อ่านจาก stdinอ่านจาก

ถ้าเราใช้cat -เมื่อไม่มีstdinมันจะรอตลอดไปนั่นคือเหตุผลที่เราใส่ไว้ในifสภาพ


นอกจากนี้คุณยังสามารถใช้/dev/stdinซึ่งเป็นลิงค์ไปยัง/proc/self/fd/0
Elie G.

3

การวางบางสิ่งบางอย่างลงในนิพจน์ที่เกี่ยวข้องกับการมอบหมายนั้นจะไม่ทำงานเช่นนั้น

ให้ลอง:

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

2

รหัสต่อไปนี้:

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

จะทำงานด้วย แต่จะเปิด sub-shell ใหม่อีกอันหลังไพพ์

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

เคยชิน.


ฉันต้องปิดใช้งานการควบคุมงานเพื่อใช้วิธีการของchepnars (ฉันใช้คำสั่งนี้จากเทอร์มินัล):

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

คู่มือทุบตีพูดว่า :

lastpipe

หากตั้งค่าไว้และการควบคุมงานไม่แอ็คทีฟเชลล์จะรันคำสั่งสุดท้ายของไพพ์ไลน์ที่ไม่ได้ดำเนินการในเบื้องหลังในสภาวะแวดล้อมเชลล์ปัจจุบัน

หมายเหตุ: การควบคุมงานจะถูกปิดโดยปริยายในเชลล์ที่ไม่มีการโต้ตอบดังนั้นคุณไม่จำเป็นต้องset +mใช้สคริปต์ภายใน


1

ฉันคิดว่าคุณกำลังพยายามเขียนเชลล์สคริปต์ซึ่งสามารถรับข้อมูลจาก stdin ได้ แต่ในขณะที่คุณพยายามที่จะทำมันแบบอินไลน์คุณได้หายไปพยายามที่จะสร้างการทดสอบ = ตัวแปร ฉันคิดว่ามันไม่สมเหตุสมผลเลยที่จะทำแบบอินไลน์และนั่นเป็นสาเหตุที่มันไม่ทำงานอย่างที่คุณคาดหวัง

ฉันพยายามลด

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

เพื่อรับสายเฉพาะจากอินพุตต่างๆ ดังนั้นฉันสามารถพิมพ์ ...

cat program_file.c | line 34

ดังนั้นฉันต้องการโปรแกรมเชลล์ขนาดเล็กที่สามารถอ่านได้จาก stdin เหมือนที่คุณทำ

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 ~ $ 

ไปแล้ว


-1

เกี่ยวกับสิ่งนี้:

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

โดยทั่วไปล่อจากคำตอบของฉันด้านล่าง
djanowski

บางทีฉันจะเถียงว่าฉันสะอาดกว่าเล็กน้อยและใกล้กับรหัสที่โพสต์ในคำถามเดิม
bingles
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.