Đọc các giá trị vào một biến shell từ một đường ống


205

Tôi đang cố gắng để có được bash để xử lý dữ liệu từ stdin được đưa vào, nhưng không có may mắn. Ý tôi là không có công việc nào sau đây:

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=

nơi tôi muốn đầu ra được test=hello world. Tôi đã thử đặt dấu ngoặc kép "" xung quanh "$test"nó cũng không hoạt động.


1
Ví dụ của bạn .. echo "hello world" | đọc kiểm tra; echo test = $ test hoạt động tốt với tôi .. result: test = hello world; môi trường nào đang chạy này dưới? Tôi đang sử dụng bash 4.2 ..
alex.pilon

Bạn có muốn nhiều dòng trong một lần đọc không? Ví dụ của bạn chỉ hiển thị một dòng, nhưng mô tả vấn đề không rõ ràng.
Charles Duffy

2
@ alex.pilon, tôi đang chạy phiên bản Bash 4.2.25 và ví dụ của anh ấy cũng không phù hợp với tôi. Có thể đó là vấn đề của tùy chọn thời gian chạy Bash hoặc biến môi trường? Tôi cũng có ví dụ không hoạt động với Sh, vì vậy Bash có thể cố gắng tương thích với Sh không?
Hibou57

2
@ Hibou57 - Tôi đã thử lại điều này trong bash 4.3.25 và nó không còn hoạt động nữa. Trí nhớ của tôi mờ nhạt về điều này và tôi không chắc mình có thể làm gì để nó hoạt động.
alex.pilon

2
@ Hibou57 @ alex.pilon cmd cuối cùng trong một đường ống nên ảnh hưởng đến vars trong bash4> = 4.2 với shopt -s lastpipe- tldp.org/LDP/abs/html/bashver4.html#LASTPIPEOPT
imz - Ivan Zakharyaschev

Câu trả lời:


162

Sử dụng

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

Bạn có thể lừa readđể chấp nhận từ một đường ống như thế này:

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

hoặc thậm chí viết một hàm như thế này:

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

Nhưng không có điểm nào - bài tập biến của bạn có thể không kéo dài! Một đường ống có thể sinh ra một lớp con, trong đó môi trường được kế thừa theo giá trị, không phải bằng tham chiếu. Đây là lý do tại sao readkhông bận tâm với đầu vào từ một đường ống - nó không được xác định.

FYI, http://www.etalabs.net/sh_tricks.html là một bộ sưu tập tiện lợi cần thiết để chống lại sự kỳ quặc và không tương thích của vỏ bourne, sh.


Bạn có thể thực hiện bài tập cuối cùng bằng cách thực hiện việc này thay vào đó: `test =` `echo" hello world "| {đọc thử; kiểm tra tiếng vang $; } `` `
Compholio

2
Hãy thử lại lần nữa (dường như thoát khỏi backticks trong lần đánh dấu này rất thú vị):test=`echo "hello world" | { read test; echo $test; }`
Compholio

Tôi có thể hỏi tại sao bạn sử dụng {}thay vì ()nhóm hai lệnh đó không?
Jürgen Paul

4
Thủ thuật không phải là tạo ra readđầu vào từ đường ống, mà là sử dụng biến trong cùng một vỏ thực thi read.
chepner

1
bash permission deniedkhi cố gắng sử dụng giải pháp này. Trường hợp của tôi khá khác nhau, nhưng tôi không thể tìm thấy câu trả lời cho nó ở bất cứ đâu, điều làm việc cho tôi là (một ví dụ khác nhưng cách sử dụng tương tự) : pip install -U echo $(ls -t *.py | head -1). Trong trường hợp, ai đó đã từng có một vấn đề tương tự và vấp phải câu trả lời như tôi.
ivan_bilan

111

nếu bạn muốn đọc nhiều dữ liệu và làm việc trên từng dòng riêng biệt, bạn có thể sử dụng một cái gì đó như thế này:

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

nếu bạn muốn chia các dòng thành nhiều từ, bạn có thể sử dụng nhiều biến thay cho x như thế này:

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

cách khác:

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

Nhưng ngay khi bạn bắt đầu muốn làm bất cứ điều gì thực sự thông minh với loại điều này, bạn nên sử dụng một số ngôn ngữ kịch bản như perl nơi bạn có thể thử một thứ như thế này:

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

Có một đường cong học tập khá dốc với perl (hoặc tôi đoán bất kỳ ngôn ngữ nào trong số này) nhưng về lâu dài bạn sẽ thấy nó dễ dàng hơn nhiều nếu bạn muốn làm bất cứ điều gì ngoại trừ các tập lệnh đơn giản nhất. Tôi muốn giới thiệu Perl Cookbook và, tất nhiên, Ngôn ngữ lập trình Perl của Larry Wall và cộng sự.


8
"Cách khác" là cách chính xác. Không có UUoC và không có subshell. Xem BashFAQ / 024 .
Tạm dừng cho đến khi có thông báo mới.

43

readsẽ không đọc từ một đường ống (hoặc có thể kết quả bị mất vì đường ống tạo ra một đường con). Tuy nhiên, bạn có thể sử dụng chuỗi ở đây trong Bash:

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

Nhưng hãy xem câu trả lời của @ chepner để biết thông tin về lastpipe.


Đẹp và đơn giản một lớp lót, dễ hiểu. Câu trả lời này cần nhiều upvote hơn.
David park

42

Đây là một lựa chọn khác

$ read test < <(echo hello world)

$ echo $test
hello world

24
Ưu điểm đáng kể <(..)$(..)được là <(..)trả về từng dòng cho người gọi ngay khi lệnh mà nó thực thi làm cho nó có sẵn. $(..)tuy nhiên, đợi lệnh hoàn thành và tạo tất cả đầu ra của nó trước khi nó cung cấp bất kỳ đầu ra nào cho người gọi.
Derek Mahar

37

Tôi không phải là chuyên gia về Bash, nhưng tôi tự hỏi tại sao điều này không được đề xuất:

stdin=$(cat)

echo "$stdin"

Bằng chứng một lớp lót cho nó hoạt động với tôi:

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

4
Điều đó có thể là do "đọc" là một lệnh bash và cat là một nhị phân riêng biệt sẽ được khởi chạy trong một quy trình con, do đó nó kém hiệu quả hơn.
dj_segfault

10
đôi khi sự đơn giản và hiệu quả của sự rõ ràng :)
Rondo

5
chắc chắn là câu trả lời thẳng thắn nhất
drwatsoncode

Vấn đề tôi gặp phải với vấn đề này là nếu một đường ống không được sử dụng, tập lệnh bị treo.
Dale Anderson

@DaleA Vâng, tất nhiên. Bất kỳ chương trình nào cố gắng đọc từ đầu vào tiêu chuẩn sẽ "treo" nếu không có gì được cung cấp cho nó.
djanowski

27

bash4.2 giới thiệu lastpipetùy chọn, cho phép mã của bạn hoạt động như được viết, bằng cách thực hiện lệnh cuối cùng trong một đường ống trong trình bao hiện tại, thay vì một lớp con.

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

2
Ah! thật tốt nếu thử nghiệm trong trình bao tương tác, cũng: "set + m" (không bắt buộc trong tập lệnh .sh)
XXL

14

Cú pháp cho một đường ống ngầm từ lệnh shell vào biến bash là

var=$(command)

hoặc là

var=`command`

Trong ví dụ của bạn, bạn đang chuyển dữ liệu đến một câu lệnh gán, điều này không mong đợi bất kỳ đầu vào nào.


4
Bởi vì $ () có thể được lồng dễ dàng. Hãy suy nghĩ trong JAVA_DIR = $ (dirname $ (readlink -f $ (java))) và thử nó với `. Bạn sẽ cần phải trốn thoát ba lần!
albfan

8

Nỗ lực đầu tiên khá gần. Biến thể này sẽ hoạt động:

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

và đầu ra là:

kiểm tra = xin chào thế giới

Bạn cần niềng răng sau đường ống để kèm theo bài tập để kiểm tra và tiếng vang.

Không có dấu ngoặc nhọn, phép gán để kiểm tra (sau đường ống) nằm trong một vỏ và tiếng vang "test = $ test" nằm trong một vỏ riêng mà không biết về phép gán đó. Đó là lý do tại sao bạn nhận được "test =" trong đầu ra thay vì "test = hello world".


8

Trong mắt tôi cách tốt nhất để đọc từ stdin trong bash là cách sau, nó cũng cho phép bạn làm việc trên các dòng trước khi đầu vào kết thúc:

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

1
Tôi gần như phát điên trước khi tìm thấy điều này. Cảm ơn rất nhiều vì đã chia sẻ!
Samy Dindane

6

Bởi vì tôi yêu nó, tôi muốn bỏ một ghi chú. Tôi tìm thấy chủ đề này, bởi vì tôi phải viết lại một tập lệnh sh cũ để tương thích POSIX. Điều này về cơ bản có nghĩa là phá vỡ vấn đề đường ống / vỏ con được giới thiệu bởi POSIX bằng cách viết lại mã như thế này:

some_command | read a b c

vào:

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

Và mã như thế này:

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

vào:

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

Nhưng cái sau không hành xử giống nhau trên đầu vào trống. Với ký hiệu cũ, vòng lặp while không được nhập vào đầu vào trống, nhưng trong ký hiệu POSIX thì có! Tôi nghĩ rằng đó là do dòng mới trước EOF, không thể bị loại bỏ. Mã POSIX hoạt động giống như ký hiệu cũ trông như thế này:

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

Trong hầu hết các trường hợp, điều này là đủ tốt. Nhưng thật không may, điều này vẫn hoạt động không chính xác như ký hiệu cũ nếu some_command in một dòng trống. Trong ký hiệu cũ, phần thân được thực thi và trong ký hiệu POSIX, chúng ta ngắt trước phần thân.

Một cách tiếp cận để khắc phục điều này có thể trông như thế này:

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

Một tập lệnh thông minh có thể vừa đọc dữ liệu từ PIPE và đối số dòng lệnh:

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

Đầu ra:

$ bash test arg1 arg2
ARGS=arg1 arg2

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

Giải thích: Khi một tập lệnh nhận bất kỳ dữ liệu nào qua đường ống, thì stdin / Proc / self / fd / 0 sẽ là một liên kết tượng trưng đến một đường ống.

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

Nếu không, nó sẽ trỏ đến thiết bị đầu cuối hiện tại:

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

[[ -pTùy chọn bash có thể kiểm tra xem nó có phải là ống hay không.

cat -đọc từ stdin.

Nếu chúng ta sử dụng cat -khi không có stdin, nó sẽ đợi mãi, đó là lý do tại sao chúng ta đặt nó trong ifđiều kiện.


Bạn cũng có thể sử dụng /dev/stdinliên kết đến/proc/self/fd/0
Elie G.

3

Đưa một cái gì đó vào một biểu thức liên quan đến một bài tập không hành xử như vậy.

Thay vào đó, hãy thử:

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

2

Các mã sau đây:

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

cũng sẽ hoạt động, nhưng nó sẽ mở một lớp vỏ mới khác sau đường ống, nơi

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

sẽ không


Tôi đã phải vô hiệu hóa kiểm soát công việc để sử dụng phương thức của chepnars (Tôi đang chạy lệnh này từ thiết bị đầu cuối):

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

Hướng dẫn Bash nói :

cuối cùng

Nếu được đặt và điều khiển công việc không hoạt động , shell sẽ chạy lệnh cuối cùng của đường ống không được thực thi trong nền trong môi trường shell hiện tại.

Lưu ý: điều khiển công việc bị tắt theo mặc định trong trình bao không tương tác và do đó bạn không cần set +mtập lệnh bên trong.


1

Tôi nghĩ rằng bạn đã cố gắng viết một kịch bản shell có thể lấy đầu vào từ stdin. nhưng trong khi bạn đang cố gắng thực hiện nội tuyến, bạn đã bị mất khi cố gắng tạo biến test = đó. Tôi nghĩ rằng nó không có ý nghĩa gì khi thực hiện nội tuyến và đó là lý do tại sao nó không hoạt động theo cách bạn mong đợi.

Tôi đã cố gắng giảm

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

để có được một dòng cụ thể từ đầu vào khác nhau. vì vậy tôi có thể gõ ...

cat program_file.c | line 34

Vì vậy, tôi cần một chương trình shell nhỏ có thể đọc từ stdin. như bạn làm

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

bạn đi đây


-1

Còn cái này thì sao:

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

Về cơ bản là một bản dupe từ câu trả lời của tôi dưới đây.
djanowski

Có lẽ, tôi sẽ tranh luận của tôi là sạch hơn một chút và gần hơn với mã được đăng trong câu hỏi ban đầu.
trận đấu
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.