Câu hỏi Làm thế nào để tách một chuỗi trên một dấu phân tách trong Bash?


Tôi có chuỗi này được lưu trữ trong một biến:

IN="bla@some.com;john@home.com"

Bây giờ tôi muốn chia các chuỗi bằng ; dấu phân cách để tôi có:

ADDR1="bla@some.com"
ADDR2="john@home.com"

Tôi không nhất thiết cần ADDR1 và ADDR2 biến. Nếu chúng là các phần tử của một mảng thậm chí còn tốt hơn.


Sau khi gợi ý từ các câu trả lời dưới đây, tôi đã kết thúc với những điều sau đây là những gì tôi đã sau:

#!/usr/bin/env bash

IN="bla@some.com;john@home.com"

mails=$(echo $IN | tr ";" "\n")

for addr in $mails
do
    echo "> [$addr]"
done

Đầu ra:

> [bla@some.com]
> [john@home.com]

Đã có giải pháp liên quan đến cài đặt Internal_field_separator (IFS) đến ;. Tôi không chắc điều gì đã xảy ra với câu trả lời đó, làm cách nào để bạn đặt lại IFS trở về mặc định?

RE: IFS giải pháp, tôi đã thử nó và nó hoạt động, tôi giữ cái cũ IFS và sau đó khôi phục lại nó:

IN="bla@some.com;john@home.com"

OIFS=$IFS
IFS=';'
mails2=$IN
for x in $mails2
do
    echo "> [$x]"
done

IFS=$OIFS

BTW, khi tôi cố gắng

mails2=($IN)

Tôi chỉ nhận được chuỗi đầu tiên khi in nó trong vòng lặp, mà không có dấu ngoặc xung quanh $IN nó hoạt động.


1514
2018-05-28 02:03


gốc


Liên quan đến "Edit2" của bạn: Bạn có thể chỉ đơn giản là "unset IFS" và nó sẽ trở về trạng thái mặc định. Không cần phải lưu và khôi phục nó một cách rõ ràng trừ khi bạn có một số lý do để mong đợi rằng nó đã được đặt thành một giá trị không mặc định. Hơn nữa, nếu bạn đang làm điều này bên trong một hàm (và, nếu bạn không, tại sao không?), Bạn có thể đặt IFS làm biến cục bộ và nó sẽ trở về giá trị trước đó của nó khi bạn thoát khỏi hàm. - Brooks Moses
@BrooksMoses: (a) +1 cho việc sử dụng local IFS=... có thể ở đâu; (b) -1 cho unset IFS, điều này không chính xác đặt lại IFS thành giá trị mặc định của nó, mặc dù tôi tin rằng một IFS không hoạt động giống như giá trị mặc định của IFS ($ '\ t \ n'), tuy nhiên có vẻ như không hợp lệ để giả định rằng mã của bạn sẽ không bao giờ được gọi với IFS được đặt thành giá trị tùy chỉnh; (c) một ý tưởng khác là gọi một subshell: (IFS=$custom; ...) khi subshell thoát IFS sẽ trở lại bất kỳ thứ gì ban đầu. - dubiousjim
Tôi chỉ muốn có một cái nhìn nhanh về các đường dẫn để quyết định nơi để ném một tập tin thực thi, vì vậy tôi đã sử dụng để chạy ruby -e "puts ENV.fetch('PATH').split(':')". Nếu bạn muốn ở lại bash thuần túy sẽ không giúp đỡ, nhưng bằng cách sử dụng bất kỳ ngôn ngữ kịch bản nàocó tính năng chia tích hợp dễ dàng hơn. - nicooga
Đây là loại bình luận theo ổ đĩa, nhưng vì OP sử dụng địa chỉ email làm ví dụ, có ai bực mình trả lời nó theo cách hoàn toàn phù hợp với RFC 5322, cụ thể là bất kỳ chuỗi trích dẫn nào có thể xuất hiện trước @ sẽ cần các biểu thức chính quy hoặc một số loại phân tích cú pháp khác thay vì sử dụng IFS ngây thơ hoặc các chức năng chia nhỏ đơn giản khác. - Jeff
for x in $(IFS=';';echo $IN); do echo "> [$x]"; done - user2037659


Các câu trả lời:


Bạn có thể đặt bộ tách trường bên trong (IFS) biến, và sau đó để cho nó phân tích thành một mảng. Khi điều này xảy ra trong một lệnh, sau đó gán cho IFS chỉ xảy ra với môi trường của lệnh đơn đó (để read ). Sau đó nó phân tích cú pháp đầu vào theo IFS biến giá trị thành một mảng, mà sau đó chúng ta có thể lặp lại.

IFS=';' read -ra ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
    # process "$i"
done

Nó sẽ phân tích một dòng các mục được phân cách bởi ;, đẩy nó vào một mảng. Nội dung để xử lý toàn bộ $IN, mỗi lần một dòng đầu vào được phân tách bằng ;:

 while IFS=';' read -ra ADDR; do
      for i in "${ADDR[@]}"; do
          # process "$i"
      done
 done <<< "$IN"

927
2018-05-28 02:23



Đây có lẽ là cách tốt nhất. IFS sẽ tồn tại bao lâu trong giá trị hiện tại của nó, nó có thể làm rối tung mã của tôi bằng cách được thiết lập khi nó không được, và làm thế nào tôi có thể thiết lập lại nó khi tôi làm xong với nó? - Chris Lutz
bây giờ sau khi sửa chữa được áp dụng, chỉ trong thời hạn của lệnh đọc :) - Johannes Schaub - litb
Bạn có thể đọc tất cả mọi thứ cùng một lúc mà không cần sử dụng vòng lặp while: read -r -d '' -a addr <<< "$ in" # The -d '' là khóa ở đây, nó cho biết không được dừng ở dòng đầu tiên ( đó là mặc định -d) nhưng để tiếp tục cho đến EOF hoặc một byte NULL (chỉ xuất hiện trong dữ liệu nhị phân). - lhunath
@LucaBorrione Setting IFS trên cùng một dòng với read không có dấu chấm phẩy hoặc dấu tách khác, trái ngược với trong một lệnh riêng biệt, phạm vi nó đến lệnh đó - vì vậy nó luôn được "khôi phục"; bạn không cần phải làm bất cứ điều gì bằng tay. - Charles Duffy
@imagineerThis Có một lỗi liên quan đến herestrings và thay đổi địa phương để IFS yêu cầu $IN để được trích dẫn. Lỗi được sửa trong bash 4.3. - chepner


Được lấy từ Mảng phân tách tập lệnh shell Bash:

IN="bla@some.com;john@home.com"
arrIN=(${IN//;/ })

Giải trình:

Công trình này thay thế tất cả các lần xuất hiện của ';' (ban đầu // có nghĩa là thay thế toàn cục) trong chuỗi IN với ' ' (một dấu cách), sau đó diễn giải chuỗi được phân tách bằng dấu cách như một mảng (đó là những gì các dấu ngoặc đơn xung quanh làm).

Cú pháp được sử dụng bên trong các dấu ngoặc nhọn để thay thế mỗi ';' nhân vật với một ' ' nhân vật được gọi là Mở rộng tham số.

Có một số gotchas phổ biến:

  1. Nếu chuỗi gốc có dấu cách, bạn sẽ cần phải sử dụng IFS:
    • IFS=':'; arrIN=($IN); unset IFS;
  2. Nếu chuỗi gốc có dấu cách  dấu phân tách là một dòng mới, bạn có thể đặt IFS với:
    • IFS=$'\n'; arrIN=($IN); unset IFS;

744
2018-03-10 09:00



Tôi chỉ muốn thêm: đây là cách đơn giản nhất, bạn có thể truy cập các phần tử mảng với $ {arrIN [1]} (bắt đầu từ số không của khóa học) - Oz123
Tìm thấy nó: kỹ thuật sửa đổi một biến trong một $ {} được gọi là 'mở rộng tham số'. - KomodoDave
Nó có hoạt động khi chuỗi gốc chứa không gian không? - qbolec
Không, tôi không nghĩ rằng điều này hoạt động khi cũng có không gian hiện diện ... nó chuyển đổi ',' thành '' và sau đó xây dựng một mảng cách nhau bằng dấu cách. - Ethan
Đây là một cách tiếp cận xấu vì các lý do khác: Ví dụ: nếu chuỗi của bạn chứa ;*;, sau đó là * sẽ được mở rộng thành danh sách tên tệp trong thư mục hiện tại. -1 - Charles Duffy


Nếu bạn không nhớ xử lý chúng ngay lập tức, tôi thích làm điều này:

for i in $(echo $IN | tr ";" "\n")
do
  # process
done

Bạn có thể sử dụng loại vòng lặp này để khởi tạo một mảng, nhưng có lẽ một cách dễ dàng hơn để làm điều đó. Hy vọng điều này sẽ giúp, mặc dù.


207
2018-05-28 02:09



Bạn nên giữ câu trả lời IFS. Nó dạy tôi điều gì đó mà tôi không biết, và nó chắc chắn tạo ra một mảng, trong khi điều này chỉ làm cho một sự thay thế rẻ tiền. - Chris Lutz
Tôi hiểu rồi. Vâng tôi tìm thấy những thí nghiệm ngớ ngẩn này, tôi sẽ học những điều mới mỗi khi tôi cố gắng trả lời mọi thứ. Tôi đã chỉnh sửa nội dung dựa trên phản hồi #bash IRC và hủy xóa :) - Johannes Schaub - litb
-1, bạn rõ ràng không nhận thức được từ ngữ, bởi vì nó giới thiệu hai lỗi trong mã của bạn. một là khi bạn không báo giá $ IN và cách khác là khi bạn giả vờ một dòng mới là dấu phân cách duy nhất được sử dụng trong từ vựng. Bạn đang lặp lại trên mọi WORD trong IN, không phải mọi dòng, và xác định không phải mọi phần tử được giới hạn bởi dấu chấm phẩy, mặc dù nó có vẻ như có tác dụng phụ giống như nó hoạt động. - lhunath
Bạn có thể thay đổi nó để echo "$ IN" | tr ';' '\ n' | trong khi đọc -r ADDY; # xử lý "$ ADDY"; thực hiện để làm cho anh ta may mắn, tôi nghĩ :) Lưu ý rằng điều này sẽ ngã ba, và bạn không thể thay đổi các biến bên ngoài từ bên trong vòng lặp (đó là lý do tại sao tôi sử dụng cú pháp <<< "$ IN") - Johannes Schaub - litb
Để tóm tắt các cuộc tranh luận trong các ý kiến: Hãy cẩn thận để sử dụng chung: shell áp dụng tách từ và mở rộng vào chuỗi, có thể không mong muốn; chỉ cần thử nó với. IN="bla@some.com;john@home.com;*;broken apart". Trong ngắn hạn: cách tiếp cận này sẽ phá vỡ, nếu mã thông báo của bạn có chứa không gian nhúng và / hoặc ký tự. nhu la * xảy ra để tạo một tên tệp khớp với mã thông báo trong thư mục hiện tại. - mklement0


Câu trả lời tương thích

Đối với câu hỏi SO này, đã có rất nhiều cách khác nhau để thực hiện điều này trong . Nhưng bash có nhiều đặc biệt các tính năng, được gọi là bashism hoạt động tốt, nhưng điều đó sẽ không hoạt động ở bất kỳ .

Đặc biệt, mảng, mảng kết hợp-sự thay thế mẫu tinh khiết bashisms và có thể không hoạt động dưới khác vỏ sò.

Về tôi Debian GNU / Linux, đây là một Tiêu chuẩn vỏ được gọi là nhưng tôi biết nhiều người thích sử dụng .

Cuối cùng, trong tình huống rất nhỏ, có một công cụ đặc biệt gọi là  với thông dịch viên vỏ của riêng mình ().

Chuỗi được yêu cầu

Chuỗi mẫu trong câu hỏi SO là:

IN="bla@some.com;john@home.com"

Vì điều này có thể hữu ích với khoảng trắng và như khoảng trắng có thể sửa đổi kết quả của thói quen, tôi thích sử dụng chuỗi mẫu này:

 IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"

Tách chuỗi dựa trên dấu phân tách trong  (phiên bản> = 4.2)

Dưới nguyên chất bash, chúng ta có thể sử dụng mảng và IFS:

var="bla@some.com;john@home.com;Full Name <fulnam@other.org>"

oIFS="$IFS"
IFS=";"
declare -a fields=($var)
IFS="$oIFS"
unset oIFS


123
2018-04-13 14:20



Các #, ##, %và %% thay thế có IMO là một lời giải thích dễ dàng hơn để nhớ (cho bao nhiêu họ xóa): # và % xóa chuỗi đối sánh ngắn nhất có thể và ## và %% xóa lâu nhất có thể. - Score_Under
Các IFS=\; read -a fields <<<"$var" không thành công trên các dòng mới và thêm một dòng mới. Giải pháp khác loại bỏ một trường trống ở cuối. - sorontar
Dấu phân cách vỏ là câu trả lời thanh lịch nhất, thời gian. - Eric Chen


Cách tiếp cận này:

IN="bla@some.com;john@home.com" 
set -- "$IN" 
IFS=";"; declare -a Array=($*) 
echo "${Array[@]}" 
echo "${Array[0]}" 
echo "${Array[1]}" 

Nguồn


80
2018-05-28 10:31



1 ... nhưng tôi sẽ không đặt tên biến "Array" ... pet peev Tôi đoán vậy. Giải pháp tốt. - Yzmir Ramirez
+1 ... nhưng "set" và khai báo -a là không cần thiết. Bạn cũng có thể đã sử dụng IFS";" && Array=($IN) - ata
+1 Chỉ một lưu ý phụ: không nên đề xuất để giữ IFS cũ và sau đó khôi phục nó? (như được hiển thị bởi stefanB trong edit3 của mình) mọi người đổ bộ ở đây (đôi khi chỉ sao chép và dán một giải pháp) có thể không nghĩ về điều này - Luca Borrione
-1: Đầu tiên, @ata là đúng mà hầu hết các lệnh trong này không làm gì cả. Thứ hai, nó sử dụng tách từ để tạo thành mảng, và không làm bất cứ điều gì để ngăn chặn việc mở rộng glob khi làm như vậy (vì vậy nếu bạn có các ký tự glob trong bất kỳ phần tử mảng nào, thì các phần tử đó được thay thế bằng tên tệp phù hợp). - Charles Duffy
Đề xuất sử dụng $'...': IN=$'bla@some.com;john@home.com;bet <d@\ns* kl.com>'. Sau đó echo "${Array[2]}" sẽ in một chuỗi với dòng mới. set -- "$IN" cũng cần thiết trong trường hợp này. Có, để ngăn chặn việc mở rộng glob, giải pháp nên bao gồm set -f. - John_West


Tôi đã nhìn thấy một vài câu trả lời tham chiếu cut lệnh, nhưng tất cả chúng đều đã bị xóa. Đó là một chút kỳ lạ mà không ai đã xây dựng trên đó, bởi vì tôi nghĩ rằng đó là một trong những lệnh hữu ích hơn để làm điều này loại điều, đặc biệt là để phân tích các tập tin đăng nhập phân cách.

Trong trường hợp tách ví dụ cụ thể này thành mảng bash script, tr có lẽ hiệu quả hơn, nhưng cut có thể được sử dụng và hiệu quả hơn nếu bạn muốn kéo các trường cụ thể từ giữa.

Thí dụ:

$ echo "bla@some.com;john@home.com" | cut -d ";" -f 1
bla@some.com
$ echo "bla@some.com;john@home.com" | cut -d ";" -f 2
john@home.com

Bạn rõ ràng có thể đặt nó vào một vòng lặp và lặp lại tham số -f để kéo từng trường một cách độc lập.

Điều này sẽ hữu ích hơn khi bạn có tệp nhật ký được phân tách bằng các hàng như sau:

2015-04-27|12345|some action|an attribute|meta data

cut rất tiện dụng để có thể cat tệp này và chọn một trường cụ thể để xử lý thêm.


75
2018-04-27 18:20



Kudo để sử dụng cut, đó là công cụ thích hợp cho công việc! Xóa nhiều hơn bất kỳ vỏ bọc nào. - MisterMiyagi
Cách tiếp cận này sẽ chỉ hoạt động nếu bạn biết số lượng các yếu tố trước; bạn cần lập trình một số logic khác xung quanh nó. Nó cũng chạy một công cụ bên ngoài cho mọi phần tử. - uli42
Excatly waht tôi đã tìm cách cố gắng tránh chuỗi rỗng trong một csv. Bây giờ tôi có thể chỉ ra giá trị 'cột' chính xác. Làm việc với IFS đã được sử dụng trong một vòng lặp. Tốt hơn dự kiến ​​cho tình hình của tôi. - Louis Loudog Trottier


Điều này làm việc cho tôi:

string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2

67
2017-08-11 20:45



đây là loại và ngọt ngào :) - Pardeep Sharma
Cảm ơn ... Đã giúp rất nhiều - space earth
cắt chỉ hoạt động với một char duy nhất làm dấu phân cách. - mojjj


echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g'
bla@some.com
john@home.com

59
2018-05-28 02:12



-1 nếu chuỗi chứa dấu cách thì sao? ví dụ IN="this is first line; this is second line" arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) ) sẽ tạo ra một mảng gồm 8 phần tử trong trường hợp này (một phần tử cho mỗi không gian từ được phân cách), thay vì 2 (một phần tử cho mỗi dấu hai chấm được phân tách bằng dấu phẩy) - Luca Borrione
@Luca Không kịch bản sed nào tạo ra chính xác hai dòng. Điều gì tạo ra nhiều mục nhập cho bạn là khi bạn đặt nó vào một mảng bash (mặc định chia nhỏ khoảng trắng) - lothar
Đó chính xác là điểm: OP cần lưu trữ các mục vào một mảng để lặp lại nó, như bạn có thể thấy trong các chỉnh sửa của mình. Tôi nghĩ rằng câu trả lời (tốt) của bạn đã bỏ qua đề cập đến việc sử dụng arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) ) để đạt được điều đó và để tư vấn thay đổi IFS thành IFS=$'\n' cho những người đến đây trong tương lai và cần phải phân chia một chuỗi có chứa không gian. (và khôi phục lại sau). :) - Luca Borrione
@ Luca Tốt điểm. Tuy nhiên việc gán mảng không phải là câu hỏi ban đầu khi tôi viết câu trả lời đó. - lothar


Điều này cũng hoạt động:

IN="bla@some.com;john@home.com"
echo ADD1=`echo $IN | cut -d \; -f 1`
echo ADD2=`echo $IN | cut -d \; -f 2`

Hãy cẩn thận, giải pháp này không phải lúc nào cũng chính xác. Trong trường hợp bạn chỉ chuyển "bla@some.com", nó sẽ gán nó cho cả ADD1 và ADD2.


57
2017-09-08 05:01



Bạn có thể sử dụng -s để tránh vấn đề được đề cập: superuser.com/questions/896800/…  "-f, --fields = LIST chỉ chọn những trường này; cũng in bất kỳ dòng nào không chứa ký tự dấu tách, trừ khi tùy chọn -s được chỉ định" - fersarr


Tôi nghĩ AWK là lệnh tốt nhất và hiệu quả để giải quyết vấn đề của bạn. AWK được bao gồm trong Bash theo mặc định trong hầu hết mọi bản phân phối Linux.

echo "bla@some.com;john@home.com" | awk -F';' '{print $1,$2}'

sẽ cho

bla@some.com john@home.com

Tất nhiên bạn có thể lưu trữ từng địa chỉ email bằng cách xác định lại trường in awk.


32
2018-01-14 06:33



Hoặc thậm chí đơn giản hơn: echo "bla@some.com; john@home.com" | awk 'BEGIN {RS = ";"} {print}' - Jaro
@ Jaro Điều này làm việc hoàn hảo cho tôi khi tôi đã có một chuỗi với dấu phẩy và cần phải định dạng lại nó thành dòng. Cảm ơn. - Aquarelle
Nó hoạt động trong kịch bản này -> "echo" $ SPLIT_0 "| awk -F 'inode =' '{print $ 1}'"! Tôi đã có vấn đề khi cố gắng sử dụng atrings ("inode =") thay vì ký tự (";"). $ 1, $ 2, $ 3, $ 4 được đặt làm vị trí trong một mảng! Nếu có một cách để thiết lập một mảng ... tốt hơn! Cảm ơn! - Eduardo Lucio
@EduardoLucio, những gì tôi đang nghĩ đến có lẽ trước tiên bạn có thể thay thế dấu phân tách của mình inode= vào ; ví dụ bằng sed -i 's/inode\=/\;/g' your_file_to_process, sau đó xác định -F';' khi áp dụng awk, Hy vọng có thể giúp bạn. - Tony