Câu trả lời tương thích
Có rất nhiều cách khác nhau để làm điều này trong bash.
Tuy nhiên, điều quan trọng cần lưu ý đầu tiên là bash
có nhiều tính năng đặc biệt (được gọi là bashism ) sẽ không hoạt động trong bất kỳ tính năng nào khácvỏ.
Đặc biệt, mảng , mảng kết hợp , và thay thế mô hình , được sử dụng trong các giải pháp trong bài viết này cũng như những người khác trong các chủ đề, là bashisms và có thể không làm việc dưới khác vỏ mà nhiều người sử dụng.
Ví dụ: trên Debian GNU / Linux của tôi , có một vỏ tiêu chuẩn được gọi làdấu gạch ngang; Tôi biết nhiều người thích sử dụng một vỏ khác được gọi làksh; và cũng có một công cụ đặc biệt gọi làbận rộn với trình thông dịch shell của riêng mình (tro).
Chuỗi yêu cầu
Chuỗi được phân chia trong câu hỏi trên là:
IN="bla@some.com;john@home.com"
Tôi sẽ sử dụng một phiên bản sửa đổi của chuỗi này để đảm bảo rằng giải pháp của tôi mạnh mẽ đối với các chuỗi chứa khoảng trắng, có thể phá vỡ các giải pháp khác:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
Chia chuỗi dựa trên dấu phân cách trong bash (phiên bản> = 4.2)
Trong sạch bash
, chúng ta có thể tạo một mảng với các phần tử được phân tách bằng một giá trị tạm thời cho IFS ( dấu tách trường đầu vào ). IFS, trong số những thứ khác, cho biết bash
(các) ký tự nào sẽ được coi là dấu phân cách giữa các phần tử khi xác định một mảng:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# save original IFS value so we can restore it later
oIFS="$IFS"
IFS=";"
declare -a fields=($IN)
IFS="$oIFS"
unset oIFS
Trong các phiên bản mới hơn của bash
, tiền tố một lệnh với một định nghĩa IFS thay đổi IFS cho lệnh đó chỉ và resets nó với giá trị trước đó ngay lập tức sau đó. Điều này có nghĩa là chúng ta có thể làm như trên chỉ trong một dòng:
IFS=\; read -a fields <<<"$IN"
# after this command, the IFS resets back to its previous value (here, the default):
set | grep ^IFS=
# IFS=$' \t\n'
Chúng ta có thể thấy rằng chuỗi IN
đã được lưu trữ thành một mảng có tên fields
, được phân chia trên dấu chấm phẩy:
set | grep ^fields=\\\|^IN=
# fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
# IN='bla@some.com;john@home.com;Full Name <fulnam@other.org>'
(Chúng tôi cũng có thể hiển thị nội dung của các biến này bằng cách sử dụng declare -p
:)
declare -p IN fields
# declare -- IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# declare -a fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
Lưu ý rằng đó read
là cách nhanh nhất để thực hiện phân tách vì không có nhánh hoặc tài nguyên bên ngoài được gọi.
Khi mảng được xác định, bạn có thể sử dụng một vòng lặp đơn giản để xử lý từng trường (hoặc, đúng hơn là từng phần tử trong mảng mà bạn đã xác định bây giờ):
# `"${fields[@]}"` expands to return every element of `fields` array as a separate argument
for x in "${fields[@]}" ;do
echo "> [$x]"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Hoặc bạn có thể thả từng trường từ mảng sau khi xử lý bằng cách sử dụng phương pháp dịch chuyển , mà tôi thích:
while [ "$fields" ] ;do
echo "> [$fields]"
# slice the array
fields=("${fields[@]:1}")
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Và nếu bạn chỉ muốn một bản in đơn giản của mảng, bạn thậm chí không cần phải lặp lại nó:
printf "> [%s]\n" "${fields[@]}"
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Cập nhật: gần đây bash > = 4,4
Trong các phiên bản mới hơn bash
, bạn cũng có thể chơi với lệnh mapfile
:
mapfile -td \; fields < <(printf "%s\0" "$IN")
Cú pháp này bảo tồn các ký tự đặc biệt, dòng mới và các trường trống!
Nếu bạn không muốn bao gồm các trường trống, bạn có thể làm như sau:
mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}") # drop '\n' added by '<<<'
Với mapfile
, bạn cũng có thể bỏ qua việc khai báo một mảng và ngầm "lặp" qua các phần tử được phân tách, gọi một hàm trên mỗi:
myPubliMail() {
printf "Seq: %6d: Sending mail to '%s'..." $1 "$2"
# mail -s "This is not a spam..." "$2" </path/to/body
printf "\e[3D, done.\n"
}
mapfile < <(printf "%s\0" "$IN") -td \; -c 1 -C myPubliMail
(Lưu ý: \0
ở cuối chuỗi định dạng là vô ích nếu bạn không quan tâm đến các trường trống ở cuối chuỗi hoặc chúng không có mặt.)
mapfile < <(echo -n "$IN") -td \; -c 1 -C myPubliMail
# Seq: 0: Sending mail to 'bla@some.com', done.
# Seq: 1: Sending mail to 'john@home.com', done.
# Seq: 2: Sending mail to 'Full Name <fulnam@other.org>', done.
Hoặc bạn có thể sử dụng <<<
và trong thân hàm bao gồm một số xử lý để bỏ dòng mới, nó thêm vào:
myPubliMail() {
local seq=$1 dest="${2%$'\n'}"
printf "Seq: %6d: Sending mail to '%s'..." $seq "$dest"
# mail -s "This is not a spam..." "$dest" </path/to/body
printf "\e[3D, done.\n"
}
mapfile <<<"$IN" -td \; -c 1 -C myPubliMail
# Renders the same output:
# Seq: 0: Sending mail to 'bla@some.com', done.
# Seq: 1: Sending mail to 'john@home.com', done.
# Seq: 2: Sending mail to 'Full Name <fulnam@other.org>', done.
Chia chuỗi dựa trên dấu phân cách trong vỏ
Nếu bạn không thể sử dụng bash
hoặc nếu bạn muốn viết một cái gì đó có thể được sử dụng trong nhiều shell khác nhau, bạn thường không thể sử dụng bashism - và điều này bao gồm các mảng chúng tôi đã sử dụng trong các giải pháp ở trên.
Tuy nhiên, chúng ta không cần sử dụng mảng để lặp lại "các phần tử" của chuỗi. Có một cú pháp được sử dụng trong nhiều shell để xóa các chuỗi con của chuỗi từ lần xuất hiện đầu tiên hoặc lần cuối của mẫu. Lưu ý rằng đó *
là ký tự đại diện cho không hoặc nhiều ký tự:
(Việc thiếu cách tiếp cận này trong bất kỳ giải pháp nào được đăng cho đến nay là lý do chính khiến tôi viết câu trả lời này;)
${var#*SubStr} # drops substring from start of string up to first occurrence of `SubStr`
${var##*SubStr} # drops substring from start of string up to last occurrence of `SubStr`
${var%SubStr*} # drops substring from last occurrence of `SubStr` to end of string
${var%%SubStr*} # drops substring from first occurrence of `SubStr` to end of string
Theo giải thích của Score_Under :
#
và %
xóa chuỗi con phù hợp ngắn nhất có thể từ đầu và cuối chuỗi tương ứng, và
##
và %%
xóa chuỗi con phù hợp dài nhất có thể.
Sử dụng cú pháp trên, chúng ta có thể tạo một cách tiếp cận trong đó chúng ta trích xuất các "phần tử" của chuỗi con khỏi chuỗi bằng cách xóa các chuỗi con lên đến hoặc sau dấu phân cách.
Các codeblock dưới đây hoạt động tốt trong bash(bao gồm cả Mac OS bash
),dấu gạch ngang, kshvà bận rộn'S tro:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$IN" ] ;do
# extract the substring from start of string up to delimiter.
# this is the first "element" of the string.
iter=${IN%%;*}
echo "> [$iter]"
# if there's only one element left, set `IN` to an empty string.
# this causes us to exit this `while` loop.
# else, we delete the first "element" of the string from IN, and move onto the next.
[ "$IN" = "$iter" ] && \
IN='' || \
IN="${IN#*;}"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Chúc vui vẻ!