Bắt đầu ra nhiều dòng vào một biến Bash


583

Tôi đã có một tập lệnh 'myscript' xuất ra như sau:

abc
def
ghi

trong một kịch bản khác, tôi gọi:

declare RESULT=$(./myscript)

$RESULTnhận được giá trị

abc def ghi

Có cách nào để lưu trữ kết quả bằng dòng mới hoặc với ký tự '\ n' để tôi có thể xuất kết quả bằng ' echo -e' không?


1
nó gây bất ngờ cho tôi. bạn không có $ (cat ./myscipt)? nếu không, tôi đã mong nó sẽ cố gắng thực thi các lệnh abc, def và ghi
Johannes Schaub - litb

@litb: vâng, tôi cho là vậy; bạn cũng có thể sử dụng $ (<./ myscript) để tránh thực thi lệnh.
Jonathan Leffler

2
(NB: hai ý kiến ​​trên đề cập đến việc sửa đổi câu hỏi bắt đầu Tôi đã có một tập lệnh 'myscript' có chứa phần sau , dẫn đến các câu hỏi. Bản sửa đổi hiện tại của câu hỏi ( Tôi đã có một tập lệnh ' myscript 'đưa ra kết quả như sau ) làm cho các bình luận trở nên thừa thãi. Tuy nhiên, bản sửa đổi là từ 2011-11-11, rất lâu sau khi hai bình luận được đưa ra.
Jonathan Leffler

Câu trả lời:


1082

Trên thực tế, KẾT QUẢ chứa những gì bạn muốn - để chứng minh:

echo "$RESULT"

Những gì bạn thể hiện là những gì bạn nhận được từ:

echo $RESULT

Như đã lưu ý trong các nhận xét, sự khác biệt là (1) phiên bản được trích dẫn kép của biến ( echo "$RESULT") giữ khoảng cách bên trong của giá trị chính xác như được biểu thị trong biến - dòng mới, tab, nhiều khoảng trống và tất cả - trong khi (2 ) phiên bản không trích dẫn ( echo $RESULT) thay thế từng chuỗi của một hoặc nhiều khoảng trống, tab và dòng mới bằng một khoảng trắng. Do đó (1) duy trì hình dạng của biến đầu vào, trong khi (2) tạo ra một dòng đầu ra rất dài có khả năng với 'từ' được phân tách bằng khoảng trắng (trong đó 'từ' là một chuỗi các ký tự không phải khoảng trắng; 't là bất kỳ chữ và số trong bất kỳ từ nào).


69
@troelskn: sự khác biệt là (1) phiên bản được trích dẫn kép của biến giữ khoảng cách bên trong của giá trị chính xác như được biểu thị trong biến, dòng mới, tab, nhiều khoảng trống và tất cả, trong khi (2) phiên bản không trích dẫn thay thế mỗi chuỗi gồm một hoặc nhiều khoảng trống, tab và dòng mới với một khoảng trắng. Do đó (1) duy trì hình dạng của biến đầu vào, trong khi (2) tạo ra một dòng đầu ra rất dài có khả năng với 'từ' được phân tách bằng khoảng trắng (trong đó 'từ' là một chuỗi các ký tự không phải khoảng trắng; 't là bất kỳ chữ và số trong bất kỳ từ nào).
Jonathan Leffler

23
Để làm cho câu trả lời dễ hiểu hơn: câu trả lời cho biết tiếng vang "$ RESULT" duy trì dòng mới, trong khi echo $ RESULT thì không.
Yu Shen

Điều này không bảo tồn các dòng mới và không gian hàng đầu trong một số tình huống.
Dấu phẩy

3
@CommaToast: Bạn sẽ giải thích về điều đó? Trailing newlines bị mất; không có một cách dễ dàng xung quanh đó. Khoảng trống hàng đầu - Tôi không biết về bất kỳ trường hợp nào mà họ bị mất.
Jonathan Leffler


90

Một cạm bẫy khác với điều này là sự thay thế lệnh - $()- dải các dòng mới. Có lẽ không phải lúc nào cũng quan trọng, nhưng nếu bạn thực sự muốn bảo tồn chính xác đầu ra là gì, bạn sẽ phải sử dụng một dòng khác và một số trích dẫn:

RESULTX="$(./myscript; echo x)"
RESULT="${RESULTX%x}"

Điều này đặc biệt quan trọng nếu bạn muốn xử lý tất cả tên tệp có thể (để tránh hành vi không xác định như thao tác trên tệp sai).


4
Tôi đã phải làm việc trong một thời gian với một vỏ bị hỏng mà không loại bỏ dòng mới cuối cùng khỏi sự thay thế lệnh (nó không phảiquá trình thay thế ) và nó đã phá vỡ hầu hết mọi thứ. Ví dụ: nếu bạn đã làm pwd=`pwd`; ls $pwd/$file, bạn đã có một dòng mới trước /và việc đặt tên trong dấu ngoặc kép không giúp ích gì. Nó đã được sửa chữa nhanh chóng. Điều này đã trở lại vào khung thời gian 1983-5 trên ICL Perq PNX; Shell không có $PWDbiến tích hợp.
Jonathan Leffler

19

Trong trường hợp bạn quan tâm đến các dòng cụ thể, hãy sử dụng mảng kết quả:

declare RESULT=($(./myscript))  # (..) = array
echo "First line: ${RESULT[0]}"
echo "Second line: ${RESULT[1]}"
echo "N-th line: ${RESULT[N]}"

3
Nếu có khoảng trắng trong các dòng, điều này sẽ tính các trường (nội dung giữa các khoảng trắng) thay vì các dòng.
Liam

2
Bạn sẽ sử dụng readarrayvà xử lý thay thế thay vì lệnh thay thế : readarray -t RESULT < <(./myscript>.
chepner

15

Ngoài câu trả lời được đưa ra bởi @ l0b0, tôi còn gặp phải trường hợp tôi cần giữ cả bất kỳ đầu ra dòng mới nào theo kịch bản kiểm tra mã trả về của tập lệnh. Và vấn đề với câu trả lời của l0b0 là 'echo x' đã đặt lại $? trở về số không ... vì vậy tôi đã tìm ra giải pháp rất xảo quyệt này:

RESULTX="$(./myscript; echo x$?)"
RETURNCODE=${RESULTX##*x}
RESULT="${RESULTX%x*}"

Điều này thật đáng yêu, tôi thực sự đã có trường hợp sử dụng trong đó tôi muốn có một die ()hàm chấp nhận mã thoát tùy ý và tùy chọn một tin nhắn, và tôi muốn sử dụng một usage ()chức năng khác để cung cấp tin nhắn nhưng các dòng mới tiếp tục bị xóa, hy vọng điều này cho phép tôi làm việc xung quanh đó
dragon788

1

Sau khi thử hầu hết các giải pháp ở đây, điều dễ dàng nhất tôi tìm thấy là điều hiển nhiên - sử dụng tệp tạm thời. Tôi không chắc chắn những gì bạn muốn làm với đầu ra nhiều dòng của bạn, nhưng sau đó bạn có thể xử lý từng dòng bằng cách đọc. Về điều duy nhất bạn thực sự không thể làm là dễ dàng gắn kết tất cả trong cùng một biến, nhưng đối với hầu hết các mục đích thực tế, đây là cách dễ dàng hơn để giải quyết.

./myscript.sh > /tmp/foo
while read line ; do 
    echo 'whatever you want to do with $line'
done < /tmp/foo

Hack nhanh để thực hiện hành động được yêu cầu:

result=""
./myscript.sh > /tmp/foo
while read line ; do
  result="$result$line\n"
done < /tmp/foo
echo -e $result

Lưu ý điều này thêm một dòng thêm. Nếu bạn làm việc với nó, bạn có thể viết mã xung quanh nó, tôi quá lười biếng.


EDIT: Mặc dù trường hợp này hoạt động hoàn hảo, nhưng mọi người đọc điều này nên lưu ý rằng bạn có thể dễ dàng nén stdin của mình bên trong vòng lặp while, do đó cung cấp cho bạn một tập lệnh sẽ chạy một dòng, xóa stdin và thoát. Giống như ssh sẽ làm điều đó tôi nghĩ? Tôi mới thấy nó gần đây, các ví dụ mã khác ở đây: /unix/24260/reading-lines-from-a-file-with-bash-for-vs-fter

Một lần nữa! Lần này với một filehandle khác (stdin, stdout, stderr là 0-2, vì vậy chúng ta có thể sử dụng & 3 hoặc cao hơn trong bash).

result=""
./test>/tmp/foo
while read line  <&3; do
    result="$result$line\n"
done 3</tmp/foo
echo -e $result

bạn cũng có thể sử dụng mktemp, nhưng đây chỉ là một ví dụ mã nhanh. Cách sử dụng cho mktemp trông giống như:

filenamevar=`mktemp /tmp/tempXXXXXX`
./test > $filenamevar

Sau đó sử dụng $ filenamevar giống như tên thực của tệp. Có lẽ không cần phải giải thích ở đây nhưng ai đó phàn nàn trong các bình luận.


Tôi cũng đã thử các giải pháp khác, với đề nghị đầu tiên của bạn, cuối cùng tôi cũng đã làm cho kịch bản của mình hoạt động
Kar.ma

1
Downvote: Điều này quá phức tạp và không tránh được nhiều bashcạm bẫy phổ biến .
tripleee 13/03/2017

Có ai đó đã nói với tôi về vấn đề stdin filehandle kỳ lạ vào ngày khác và tôi giống như "wow". Hãy thêm một cái gì đó thực sự nhanh chóng.
dùng1279741

Trong Bash, bạn có thể sử dụng readphần mở rộng lệnh -u 3để đọc từ mô tả tệp 3.
Jonathan Leffler

Tại sao không trong khi ... làm ... xong << (. Myscript.sh)
jtgd

0

Làm thế nào về điều này, nó sẽ đọc từng dòng đến một biến và có thể được sử dụng sau đó! giả sử đầu ra myscript được chuyển hướng đến một tệp có tên myscriptDefput

awk '{while ( (getline var < "myscript_output") >0){print var;} close ("myscript_output");}'

4
Vâng, đó không phải là bash, đó là awk.
vadipp
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.