Sử dụng đọc như một dấu nhắc bên trong một vòng lặp while được điều khiển bởi read?


9

Tôi có một trường hợp sử dụng trong đó tôi cần đọc nhiều biến khi bắt đầu mỗi lần lặp và đọc trong một đầu vào từ người dùng vào vòng lặp.

Đường dẫn có thể đến giải pháp mà tôi không biết cách khám phá -

  1. Để gán, sử dụng tập tin khác thay vì stdin
  2. Sử dụng forvòng lặp thay vì ... | while read ...... Tôi không biết cách gán nhiều biến trong forvòng lặp

    echo -e "1 2 3\n4 5 6" |\
    while read a b c; 
    do 
      echo "$a -> $b -> $c";
      echo "Enter a number:";
      read d ;
      echo "This number is $d" ; 
    done
    

Câu trả lời:


9

Nếu tôi hiểu đúng, tôi nghĩ rằng về cơ bản bạn muốn lặp qua danh sách các giá trị, và sau đó là readmột giá trị khác trong vòng lặp.

Dưới đây là một vài lựa chọn, 1 và 2 có lẽ là tốt nhất.

1. Mô phỏng mảng bằng chuỗi

Có mảng 2D sẽ tốt, nhưng Bash thực sự không thể. Nếu các giá trị của bạn không có khoảng trắng, một cách giải quyết gần đúng đó là gắn từng bộ ba số vào một chuỗi và chia các chuỗi bên trong vòng lặp:

for x in "1 2 3" "4 5 6"; do 
  read a b c <<< "$x"; 
  read -p "Enter a number: " d
  echo "$a - $b - $c - $d ";
done

Tất nhiên bạn cũng có thể sử dụng một số dấu phân cách khác, vd for x in 1:2:3 ...IFS=: read a b c <<< "$x".


2. Thay thế đường ống bằng một chuyển hướng khác để stdin miễn phí

Một khả năng khác là read a b cđọc dữ liệu từ một fd khác và hướng đầu vào vào đó (cái này sẽ hoạt động trong một vỏ tiêu chuẩn):

while read a b c <&3; do
    printf "Enter a number: "
    read d
    echo "$a - $b - $c - $d ";
done 3<<EOF
1 2 3
4 5 6
EOF

Và ở đây bạn cũng có thể sử dụng thay thế quy trình nếu bạn muốn lấy dữ liệu từ một lệnh: while read a b c <&3; ...done 3< <(echo $'1 2 3\n4 5 6')(thay thế quy trình là tính năng bash / ksh / zsh)


3. Lấy đầu vào của người dùng từ stderr thay thế

Hoặc, theo cách khác, sử dụng một đường ống như trong ví dụ của bạn, nhưng có đầu vào của người dùng readtừ stderr(fd 2) thay vì stdinđường ống đến từ đâu:

echo $'1 2 3\n4 5 6' |
while read a b c; do 
    read -u 2 -p "Enter a number: " d
    echo "$a - $b - $c - $d ";
done

Đọc từ stderrlà một chút kỳ lạ, nhưng thực sự thường hoạt động trong một phiên tương tác. (Bạn cũng có thể mở một cách rõ ràng /dev/tty, giả sử bạn muốn thực sự bỏ qua bất kỳ chuyển hướng nào, đó là những thứ như lesssử dụng để có được đầu vào của người dùng ngay cả khi dữ liệu được dẫn đến nó.)

Mặc dù sử dụng stderrnhư thế có thể không hoạt động trong mọi trường hợp và nếu bạn đang sử dụng một số lệnh bên ngoài thay vì read, ít nhất bạn cần thêm một loạt các chuyển hướng vào lệnh.

Ngoài ra, hãy xem Tại sao biến của tôi cục bộ trong vòng lặp 'trong khi đọc', nhưng không phải trong một vòng lặp có vẻ tương tự khác? cho một số vấn đề liên quan ... | while.


4. Cắt các phần của một mảng khi cần thiết

Tôi cho rằng bạn cũng có thể xấp xỉ một mảng 2D-ish bằng cách sao chép các lát của một chiều thông thường:

data=(1 2 3 
      4 5 6)

n=3
for ((i=0; i < "${#data[@]}"; i += n)); do
    a=( "${data[@]:i:n}" )
    read -p "Enter a number: " d
    echo "${a[0]} - ${a[1]} - ${a[2]} - $d "
done

Bạn cũng có thể gán ${a[0]}vv cho a, bv.v. nếu bạn muốn đặt tên cho các biến, nhưng Zsh sẽ làm điều đó độc đáo hơn nhiều .


1
thật tuyệt Và thật là một tổng quan tuyệt vời của các cách giải quyết khác nhau!
Debanjan Basu

@ StéphaneChazelas, à vâng, bạn đúng. Sử dụng stderrnhư thế là một chút icky, nhưng tôi đã có hồi ức rằng một số tiện ích làm điều đó. Nhưng tất cả những gì tôi có thể tìm thấy bây giờ là những thứ chỉ sử dụng /dev/tty. Ồ tốt
ilkkachu

Lưu ý rằng việc sử dụng <&2(cũng như </dev/tty) tránh đọc từ stdin của tập lệnh. Điều này sẽ không hoạt động printf '682\n739' | ./script. Cũng lưu ý rằng read -pchỉ làm việc trong bash.
Isaac

@Isaac, trong một bài đọc từ stderr, cũng có đường ống từ tiếng vang đến whilevòng lặp đó , vì vậy dù sao bạn cũng không thể sử dụng stdin của tập lệnh ... read -ucũng bị lỗi, nhưng có thể được thay thế bằng chuyển hướng và <<<trong cái đầu tiên cũng không đạt tiêu chuẩn, nhưng khó hơn một chút để làm việc.
ilkkachu

Tất cả có thể được giải quyết, xin vui lòng: đọc câu trả lời của tôi
Isaac

3

Chỉ có một /dev/stdin, readsẽ đọc từ nó bất cứ nơi nào nó được sử dụng (theo mặc định).

Giải pháp là sử dụng một số mô tả tệp khác thay vì 1 ( /dev/stdin).

Từ mã tương đương (trong bash) đến những gì bạn đã đăng [1] (xem bên dưới)
chỉ cần thêm 0</dev/tty(ví dụ) để đọc từ tty "thực":

while read a b c
do    read -p "Enter a number: " d  0</dev/tty   # 0<&2 is also valid
      echo "$a -> $b -> $c and ++> $d"
done  <<<"$(echo -e '1 2 3\n4 5 6')"

Thực hiện:

$ ./script
Enter a number: 789
1 -> 2 -> 3 and ++> 789
Enter a number: 333
4 -> 5 -> 6 and ++> 333

Cách khác là sử dụng 0<&2(có vẻ lạ, nhưng hợp lệ).

Lưu ý rằng việc đọc từ /dev/tty(cũng 0<&2) sẽ bỏ qua stdin của tập lệnh, điều này sẽ không đọc các giá trị từ tiếng vang:

$ echo -e "33\n44" | ./script

Các giải pháp khác

Điều cần thiết là chuyển hướng một đầu vào sang một số fd khác (mô tả tệp).
Hợp lệ trong ksh, bash và zsh:

while read -u 7 a b c
do    printf "Enter a number: "
      read d
      echo "$a -> $b -> $c and ++> $d"
done  7<<<"$(echo -e '1 2 3\n4 5 6')"

Hoặc, với exec:

exec 7<<<"$(echo -e '1 2 3\n4 5 6')"

while read -u 7 a b c
do    printf "Enter a number: "      
      read d
      echo "$a -> $b -> $c and ++> $d"
done  

exec 7>&-

Một giải pháp hoạt động trong sh ( <<<không hoạt động):

exec 7<<-\_EOT_
1 2 3
4 5 6
_EOT_

while read a b c  <&7
do    printf "Enter a number: "      
      read d
      echo "$a -> $b -> $c and ++> $d"
done  

exec 7>&-

Nhưng điều này có lẽ dễ hiểu hơn:

while read a b c  0<&7
do    printf "Enter a number: "      
      read d
      echo "$a -> $b -> $c and ++> $d"
done  7<<-\_EOT_
1 2 3
4 5 6
_EOT_

1 mã đơn giản

Mã của bạn là:

echo -e "1 2 3\n4 5 6" |\
while read a b c; 
do 
  echo "$a -> $b -> $c";
  echo "Enter a number: ";
  read d ;
  echo "This number is $d" ; 
done

Một mã đơn giản hóa (trong bash) là:

while read a b c
do    #0</dev/tty
      read -p "Enter a number: " d ;
      echo "$a -> $b -> $c and ++> $d";
done  <<<"$(echo -e '1 2 3\n4 5 6')"

Mà, nếu được thực thi, in:

$ ./script
1 -> 2 -> 3 and ++> 4 5 6

Điều này chỉ cho thấy rằng var d đang được đọc từ cùng /dev/stdin.


2

Với zsh, bạn có thể viết nó thay thế:

for a b c (
  1 2 3
  4 5 6
  'more complex' $'\n\n' '*** values ***'
) {
  read 'd?Enter a number: '
  do-something-with $a $b $c $d
}

Đối với mảng 2D, xem thêm ksh93vỏ:

a=(
  (1 2 3)
  (4 5 6)
  ('more complex' $'\n\n' '*** values ***')
)
for i in "${!a[@]}"; do
  read 'd?Enter a number: '
  do-something-with "${a[i][0]}" "${a[i][1]}" "${a[i][2]}" "$d"
done

tốt đẹp ... Tôi đang tìm kiếm một câu trả lời dựa trên bash, nhưng điều này là tốt để biết!
Debanjan Basu
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.