it-swarm-vi.com

Làm thế nào tôi có thể đọc từng dòng từ một biến trong bash?

Tôi có một biến chứa đầu ra đa dòng của một lệnh. Cách hiệu quả nhất để đọc dòng đầu ra theo dòng từ biến là gì?

Ví dụ:

jobs="$(jobs)"
if [ "$jobs" ]; then
    # read lines from $jobs
fi
53
Eugene Yarmash

Bạn có thể sử dụng vòng lặp while với quy trình thay thế:

while read -r line
do
    echo "$line"
done < <(jobs)

Một cách tối ưu để đọc biến đa dòng là đặt biến IFSprintf biến trong một dòng mới:

# Printf '%s\n' "$var" is necessary because printf '%s' "$var" on a
# variable that doesn't end with a newline then the while loop will
# completely miss the last line of the variable.
while IFS= read -r line
do
   echo "$line"
done < <(printf '%s\n' "$var")

Lưu ý: Theo shellcheck sc2031 , việc sử dụng trạm biến áp được ưu tiên hơn so với đường ống để tránh [tinh tế] tạo ra một lớp con.

Ngoài ra, vui lòng nhận ra rằng bằng cách đặt tên biến jobs nó có thể gây nhầm lẫn vì đó cũng là tên của lệnh Shell thông thường.

70
dogbane

Để xử lý đầu ra của một dòng lệnh theo dòng ( giải thích ):

jobs |
while IFS= read -r line; do
  process "$line"
done

Nếu bạn đã có dữ liệu trong một biến:

printf %s "$foo" | …

printf %s "$foo" Gần giống với echo "$foo", Nhưng in $foo Theo nghĩa đen, trong khi echo "$foo" Có thể hiểu $foo Dưới dạng tùy chọn cho lệnh echo nếu nó bắt đầu bằng - và có thể mở rộng chuỗi dấu gạch chéo ngược trong $foo trong một số shell.

Lưu ý rằng trong một số shell (ash, bash, pdksh, nhưng không phải ksh hoặc zsh), phía bên phải của một đường ống chạy trong một quy trình riêng biệt, do đó, bất kỳ biến nào bạn đặt trong vòng lặp đều bị mất. Ví dụ: tập lệnh đếm dòng sau đây in 0 trong các shell này:

n=0
printf %s "$foo" |
while IFS= read -r line; do
  n=$(($n + 1))
done
echo $n

Một cách giải quyết là đặt phần còn lại của tập lệnh (hoặc ít nhất là phần cần giá trị $n Từ vòng lặp) vào danh sách lệnh:

n=0
printf %s "$foo" | {
  while IFS= read -r line; do
    n=$(($n + 1))
  done
  echo $n
}

Nếu hành động trên các dòng không trống là đủ tốt và đầu vào không lớn, bạn có thể sử dụng chia tách Word:

IFS='
'
set -f
for line in $(jobs); do
  # process line
done
set +f
unset IFS

Giải thích: cài đặt IFS thành một dòng mới khiến việc phân tách Word chỉ xảy ra ở các dòng mới (trái ngược với bất kỳ ký tự khoảng trắng nào trong cài đặt mặc định). set -f Tắt tính năng toàn cầu hóa (tức là mở rộng ký tự đại diện), điều này sẽ xảy ra với kết quả của việc thay thế lệnh $(jobs) hoặc một thay thế thay thế $foo. Vòng lặp for hoạt động trên tất cả các phần của $(jobs), đó là tất cả các dòng không trống trong đầu ra lệnh. Cuối cùng, khôi phục cài đặt globalbing và IFS.

Vấn đề: nếu bạn sử dụng vòng lặp while, nó sẽ chạy trong subshell và tất cả các biến sẽ bị mất. Giải pháp: sử dụng cho vòng lặp

# change delimiter (IFS) to new line.
IFS_BAK=$IFS
IFS=$'\n'

for line in $variableWithSeveralLines; do
 echo "$line"

 # return IFS back if you need to split new line by spaces:
 IFS=$IFS_BAK
 IFS_BAK=
 lineConvertedToArraySplittedBySpaces=( $line )
 echo "{lineConvertedToArraySplittedBySpaces[0]}"
 # return IFS back to newline for "for" loop
 IFS_BAK=$IFS
 IFS=$'\n'

done 

# return delimiter to previous value
IFS=$IFS_BAK
IFS_BAK=
16
Dima
jobs="$(jobs)"
while IFS= read
do
    echo $REPLY
done <<< "$jobs"

Người giới thiệu:

11
l0b0

Trong các phiên bản bash gần đây, sử dụng mapfile hoặc readarray để đọc hiệu quả đầu ra lệnh thành mảng

$ readarray test < <(ls -ltrR)
$ echo ${#test[@]}
6305

Disclaimer: ví dụ khủng khiếp, nhưng bạn có thể hoàn toàn đưa ra một lệnh tốt hơn để sử dụng hơn là chính mình

10
sehe

Bạn có thể sử dụng <<< để đọc đơn giản từ biến chứa dữ liệu được phân tách bằng dòng mới:

while read -r line
do 
  echo "A line of input: $line"
done <<<"$lines"
0
Matthew Herrmann