Có cái gì đó giống như JavaScript chia tách () của JavaScript trong shell không?


18

Thật dễ dàng để sử dụng split()trong JavaScript để chia một chuỗi thành một mảng.

Thế còn kịch bản shell?

Nói rằng tôi muốn làm điều này:

$ script.sh var1_var2_var3

Khi người dùng đưa chuỗi như vậy var1_var2_var3cho script.sh, bên trong tập lệnh, nó sẽ chuyển đổi chuỗi thành một mảng như

array=( var1 var2 var3 )
for name in ${array[@]}; do
    # some code
done

1
shellbạn đang sử dụng cái gì , với bashbạn có thể làm gìIFS='_' read -a array <<< "${string}"
gwillie

perlcũng có thể làm điều đó Nó không phải là vỏ "nguyên chất", nhưng nó khá phổ biến.
Sobrique

@Sobrique Tôi cũng không biết định nghĩa kỹ thuật của shell "thuần", nhưng có node.js.
emory

Tôi có xu hướng làm việc trên 'có lẽ nó được cài đặt trên hộp linux của tôi theo mặc định' và đừng băn khoăn các chi tiết vụn vặt :)
Sobrique

Câu trả lời:


24

Các shell giống như Bourne / POSIX có toán tử chia + toàn cầu và nó được gọi mỗi khi bạn để mở rộng tham số ( $var, $-...), thay thế lệnh ( $(...)) hoặc mở rộng số học ( $((...))) không được trích dẫn trong ngữ cảnh danh sách.

Trên thực tế, bạn đã gọi nó do nhầm lẫn khi bạn đã làm for name in ${array[@]}thay vì for name in "${array[@]}". (Trên thực tế, bạn nên cẩn thận khi gọi toán tử đó như thế là do nhiều lỗi và lỗ hổng bảo mật ).

Toán tử đó được cấu hình với $IFStham số đặc biệt (để cho biết các ký tự được phân chia trên (mặc dù hãy cẩn thận rằng khoảng trắng, tab và dòng mới nhận được xử lý đặc biệt ở đó)) và -ftùy chọn tắt ( set -f) hoặc enable ( set +f) globphần.

Cũng lưu ý rằng trong khi S$IFSban đầu (trong vỏ Bourne nơi $IFSxuất phát từ) cho Separator, trong vỏ POSIX, các nhân vật trong $IFSnên thay vì được xem như delimiters hoặc Terminators (xem dưới đây để biết một ví dụ).

Vì vậy, để phân chia trên _:

string='var1_var2_var3'
IFS=_ # delimit on _
set -f # disable the glob part
array=($string) # invoke the split+glob operator

for i in "${array[@]}"; do # loop over the array elements.

Để xem sự khác biệt giữa dấu phân cáchdấu phân cách , hãy thử:

string='var1_var2_'

Điều đó sẽ phân chia nó thành var1var2chỉ (không có phần tử trống thêm).

Vì vậy, để làm cho nó tương tự như JavaScript split(), bạn cần thêm một bước:

string='var1_var2_var3'
IFS=_ # delimit on _
set -f # disable the glob part
temp=${string}_ # add an extra delimiter
array=($temp) # invoke the split+glob operator

(lưu ý rằng nó sẽ chia một phần tử trống $stringthành 1 (không phải 0 ), như của JavaScript split()).

Để xem tab điều trị đặc biệt, không gian và dòng mới nhận được, hãy so sánh:

IFS=' '; string=' var1  var2  '

(nơi bạn nhận được var1var2) với

IFS='_'; string='_var1__var2__'

nơi bạn nhận được: '', var1, '', var2, ''.

Lưu ý rằng zshshell không gọi toán tử split + global như thế trừ khi trong shhoặc kshmô phỏng. Ở đó, bạn phải gọi nó một cách rõ ràng. $=stringcho phần tách, $~stringcho phần toàn cầu ( $=~stringcho cả hai) và phần này cũng có toán tử phân tách trong đó bạn có thể chỉ định dấu phân cách:

array=(${(s:_:)string})

hoặc để bảo tồn các phần tử trống:

array=("${(@s:_:)string}")

Lưu ý rằng có sđể phân tách , không phân định (cũng với $IFS, không tuân thủ POSIX đã biết zsh). Nó khác với JavaScript split()ở chỗ một chuỗi trống được chia thành phần tử 0 (không phải 1).

Một sự khác biệt đáng chú ý với $IFS-splitting là ${(s:abc:)string}chia rẽ trên abcchuỗi, trong khi với IFS=abc, mà sẽ chia trên a, bhoặc c.

Với zshksh93, điều trị đặc biệt mà không gian, tab hoặc dòng mới nhận được có thể được loại bỏ bằng cách nhân đôi chúng vào $IFS.

Như một ghi chú lịch sử, vỏ Bourne (vỏ POSIX tổ tiên hoặc hiện đại) luôn tước bỏ các yếu tố trống rỗng. Nó cũng có một số lỗi liên quan đến việc chia tách và mở rộng $ @ với các giá trị không mặc định là $IFS. Ví dụ IFS=_; set -f; set -- $@sẽ không tương đương với IFS=_; set -f; set -- $1 $2 $3....

Tách trên regexps

Bây giờ đối với một cái gì đó gần gũi hơn với JavaScript split()có thể phân tách trên các biểu thức thông thường, bạn cần phải dựa vào các tiện ích bên ngoài.

Trong rương công cụ POSIX, awkcó một splittoán tử có thể phân chia trên các biểu thức chính quy mở rộng (những phần tử này ít nhiều là một tập hợp con của các biểu thức chính quy giống như Perl được JavaScript hỗ trợ).

split() {
  awk -v q="'" '
    function quote(s) {
      gsub(q, q "\\" q q, s)
      return q s q
    }
    BEGIN {
      n = split(ARGV[1], a, ARGV[2])
      for (i = 1; i <= n; i++) printf " %s", quote(a[i])
      exit
    }' "$@"
}
string=a__b_+c
eval "array=($(split "$string" '[_+]+'))"

Các zshvỏ có được xây dựng trong hỗ trợ cho các biểu thức Perl-tương thích thường xuyên (trong nó zsh/pcremô-đun), nhưng sử dụng nó để chia một chuỗi, mặc dù có thể là tương đối cồng kềnh.


Có bất kỳ lý do cho các phương pháp điều trị đặc biệt với tab, không gian và dòng mới?
cuonglm

1
@cuonglm, nói chung là bạn muốn chia chữ khi delimiters là khoảng trống, trong trường hợp của delimiters không trống (như để chia $PATHtrên :) trái lại, bạn thường muốn giữ phần tử rỗng. Lưu ý rằng trong trình bao Bourne, tất cả các ký tự đang được xử lý đặc biệt, kshđã thay đổi rằng chỉ có các phần trống (chỉ có khoảng trắng, tab và dòng mới) được xử lý đặc biệt.
Stéphane Chazelas

Vâng, ghi chú vỏ Bourne được thêm gần đây làm tôi ngạc nhiên. Và để hoàn thành, bạn có nên thêm ghi chú để zshđiều trị với chuỗi chứa 2 ký tự trở lên ${(s:string:)var}không? Nếu được thêm vào, tôi có thể xóa câu trả lời của mình :)
cuonglm

1
Ý bạn là gì khi "Cũng lưu ý rằng S trong $ IFS là dành cho Dấu phân cách, không phải Dấu phân cách."? Tôi hiểu các cơ chế và nó bỏ qua các dấu phân cách nhưng Schữ viết tắt của Dấu phân cách , không phân cách . Ít nhất, đó là những gì hướng dẫn sử dụng của bash của tôi nói.
terdon

@terdon, $IFSxuất phát từ vỏ Bourne nơi phân tách , ksh đã thay đổi hành vi mà không thay đổi tên. Tôi đề cập đến điều đó để nhấn mạnh rằng split+glob(ngoại trừ trong zsh hoặc pdksh) không chỉ đơn giản là phân tách nữa.
Stéphane Chazelas

7

Có, sử dụng IFSvà thiết lập nó _. Sau đó sử dụng read -ađể lưu trữ thành một mảng ( -rtắt mở rộng dấu gạch chéo ngược). Lưu ý rằng điều này là cụ thể cho bash; ksh và zsh có các tính năng tương tự với cú pháp hơi khác nhau và sh đơn giản không có biến mảng nào cả.

$ r="var1_var2_var3"
$ IFS='_' read -r -a array <<< "$r"
$ for name in "${array[@]}"; do echo "+ $name"; done
+ var1
+ var2
+ var3

Từ man bash:

đọc

-a aname

Các từ được gán cho các chỉ số tuần tự của aname biến mảng, bắt đầu từ 0. aname không được đặt trước khi bất kỳ giá trị mới nào được gán. Đối số tên khác được bỏ qua.

IFS

Dấu tách trường nội bộ được sử dụng để phân tách từ sau khi mở rộng và để phân tách các dòng thành các từ bằng lệnh dựng sẵn đọc. Giá trị mặc định là `` ''.

Lưu ý rằng readdừng lại ở dòng mới đầu tiên. Vượt qua -d ''để readtránh điều đó, nhưng trong trường hợp đó, sẽ có thêm một dòng mới ở cuối do <<<nhà điều hành. Bạn có thể xóa nó bằng tay:

IFS='_' read -r -d '' -a array <<< "$r"
array[$((${#array[@]}-1))]=${array[$((${#array[@]}-1))]%?}

Giả định $rđó không chứa ký tự dòng mới hoặc dấu gạch chéo ngược. Cũng lưu ý rằng nó sẽ chỉ hoạt động trong các phiên bản gần đây của bashshell.
Stéphane Chazelas

@ StéphaneChazelas điểm tốt. Có, đây là trường hợp "cơ bản" của một chuỗi. Đối với phần còn lại, mọi người nên đi cho câu trả lời toàn diện của bạn. Về các phiên bản của bash, read -ađã được giới thiệu trong bash 4, phải không?
fedorqui

1
xin lỗi, tôi nghĩ <<<chỉ mới được thêm vào gần đây bashnhưng có vẻ như nó đã ở đó kể từ 2.05b (2002). read -athậm chí còn già hơn thế. <<<cũng đến zshvà được hỗ trợ bởi ksh93(và mksh và yash) nhưng read -ađặc biệt là bash (nó có -Atrong ksh93, yash và zsh).
Stéphane Chazelas

@ StéphaneChazelas có cách nào "dễ dàng" để tìm thấy khi những thay đổi này xảy ra không? Tôi nói "dễ dàng" không đào sâu vào các tệp phát hành, có thể một trang hiển thị tất cả.
fedorqui

1
Tôi nhìn vào nhật ký thay đổi cho điều đó. zsh cũng có một kho lưu trữ git có lịch sử từ 3.1.5 và danh sách gửi thư của nó cũng được sử dụng để theo dõi các thay đổi.
Stéphane Chazelas
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.