Làm thế nào để thu thập đúng một mảng các dòng trong zsh


42

Tôi nghĩ rằng sau đây sẽ nhóm đầu ra của my_commandmột loạt các dòng:

IFS='\n' array_of_lines=$(my_command);

vì vậy mà $array_of_lines[1]sẽ đề cập đến dòng đầu tiên trong đầu ra của my_command, $array_of_lines[2]đến dòng thứ hai, v.v.

Tuy nhiên, lệnh trên dường như không hoạt động tốt. Nó dường như cũng phân chia đầu ra my_commandxung quanh ký tự n, như tôi đã kiểm tra print -l $array_of_lines, mà tôi tin rằng in các phần tử của một dòng theo từng dòng. Tôi cũng đã kiểm tra điều này với:

echo $array_of_lines[1]
echo $array_of_lines[2]
...

Trong lần thử thứ hai, tôi nghĩ việc thêm evalcó thể giúp:

IFS='\n' array_of_lines=$(eval my_command);

nhưng tôi đã nhận được kết quả chính xác như không có nó.

Cuối cùng, sau câu trả lời trên các phần tử List có khoảng trắng trong zsh , tôi cũng đã thử sử dụng các cờ mở rộng tham số thay vì IFSnói cho zsh cách chia đầu vào và thu thập các phần tử thành một mảng, tức là:

array_of_lines=("${(@f)$(my_command)}");

Nhưng tôi vẫn nhận được kết quả tương tự (chia tách xảy ra trên n)

Với điều này, tôi có các câu hỏi sau đây:

Q1. Các cách "thích hợp" để thu thập đầu ra của lệnh trong một mảng các dòng là gì?

Quý 2 Làm thế nào tôi có thể chỉ định IFSđể phân chia trên dòng mới?

H3. Nếu tôi sử dụng cờ mở rộng tham số như trong lần thử thứ ba ở trên (tức là sử dụng @f) để chỉ định chia tách, zsh có bỏ qua giá trị của IFSkhông? Tại sao nó không hoạt động ở trên?

Câu trả lời:


71

TL, DR:

array_of_lines=("${(@f)$(my_command)}")

Lỗi đầu tiên (→ Q2): IFS='\n'đặt IFSthành hai ký tự \n. Để đặt IFSthành một dòng mới, sử dụng IFS=$'\n'.

Lỗi thứ hai: để đặt một biến thành một giá trị mảng, bạn cần dấu ngoặc đơn xung quanh các phần tử : array_of_lines=(foo bar).

Điều này sẽ hoạt động, ngoại trừ việc nó loại bỏ các dòng trống, bởi vì khoảng trắng liên tiếp được tính là một dấu tách duy nhất:

IFS=$'\n' array_of_lines=($(my_command))

Bạn có thể giữ lại các dòng trống ngoại trừ ở cuối cùng bằng cách nhân đôi ký tự khoảng trắng trong IFS:

IFS=$'\n\n' array_of_lines=($(my_command))

Để tiếp tục theo dõi các dòng trống, bạn phải thêm một cái gì đó vào đầu ra của lệnh, bởi vì điều này xảy ra trong chính sự thay thế lệnh, không phải từ việc phân tích cú pháp.

IFS=$'\n\n' array_of_lines=($(my_command; echo .)); unset 'array_of_lines[-1]'

(giả sử đầu ra my_commandkhông kết thúc trong một dòng không phân tách; cũng lưu ý rằng bạn mất trạng thái thoát của my_command)

Lưu ý rằng tất cả các đoạn mã trên để lại IFSvới giá trị không mặc định của nó, vì vậy chúng có thể làm rối mã tiếp theo. Để giữ cài đặt IFScục bộ, hãy đặt toàn bộ vào một hàm nơi bạn khai báo IFScục bộ (ở đây cũng chú ý đến việc giữ nguyên trạng thái thoát lệnh):

collect_lines() {
  local IFS=$'\n\n' ret
  array_of_lines=($("$@"; ret=$?; echo .; exit $ret))
  ret=$?
  unset 'array_of_lines[-1]'
  return $ret
}
collect_lines my_command

Nhưng tôi khuyên bạn không nên lộn xộn IFS; thay vào đó, hãy sử dụng fcờ mở rộng để phân chia trên dòng mới (→ Q1):

array_of_lines=("${(@f)$(my_command)}")

Hoặc để bảo tồn các dòng trống ở cuối:

array_of_lines=("${(@f)$(my_command; echo .)}")
unset 'array_of_lines[-1]'

Giá trị của IFSkhông quan trọng ở đó. Tôi nghi ngờ rằng bạn đã sử dụng một lệnh tách ra IFSđể in $array_of_linestrong các bài kiểm tra của mình (→ Q3).


7
Điều này quá phức tạp! "${(@f)...}"là giống như ${(f)"..."}, nhưng theo một cách khác. (@)bên trong dấu ngoặc kép có nghĩa là năng suất của một từ trên mỗi phần tử mảng và (f)có nghĩa là cách chia thành một mảng bởi dòng mới. PS: Vui lòng liên kết đến các tài liệu
cừu bay

3
@flyingsheep, không ${(f)"..."}bỏ qua các dòng trống, "${(@f)...}"bảo quản chúng. Đó là sự phân biệt giống nhau giữa $argv"$argv[@]". Điều đó "$@"để bảo toàn tất cả các yếu tố của một mảng xuất phát từ vỏ Bourne vào cuối những năm 70.
Stéphane Chazelas

4

Hai vấn đề: đầu tiên, rõ ràng dấu ngoặc kép cũng không diễn giải dấu gạch chéo ngược (xin lỗi về điều đó :). Sử dụng $'...'dấu ngoặc kép. Và theo man zshparam, để thu thập các từ trong một mảng, bạn cần đặt chúng trong ngoặc đơn. Vì vậy, điều này hoạt động:

% touch 'a b' c d 'e f'
% IFS=$'\n' arr=($(ls)); print -l $arr
a b
c
d
e f
% print $arr[1]
a b

Tôi không thể trả lời Q3 của bạn. Tôi hy vọng tôi sẽ không bao giờ phải biết những điều bí truyền như vậy :).


-2

Bạn cũng có thể sử dụng tr để thay thế dòng mới bằng dấu cách:

lines=($(mycommand | tr '\n' ' '))
select line in ("${lines[@]}"); do
  echo "$line"
  break
done

3
Nếu các dòng chứa khoảng trắng thì sao?
don_crissti

2
Điều đó không có ý nghĩa. Cả SPC và NL đều nằm trong giá trị mặc định của $IFS. Dịch cái này sang cái kia không có gì khác biệt.
Stéphane Chazelas

Chỉnh sửa của tôi có hợp lý không? Tôi không thể làm cho nó hoạt động như cũ,
John P

(hết thời gian) Tôi thừa nhận tôi đã chỉnh sửa nó mà không thực sự hiểu ý định là gì, nhưng tôi nghĩ bản dịch là một khởi đầu tốt để phân tách dựa trên thao tác chuỗi. Tôi sẽ không dịch sang các khoảng trắng và phân chia chúng, trừ khi hành vi dự kiến ​​giống như thế echo, trong đó đầu vào là tất cả các đống từ ít nhiều được phân tách bởi ai quan tâm.
John P
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.