Tách chuỗi theo dấu phân cách và lấy phần tử N-th


75

Tôi có một chuỗi:

one_two_three_four_five

Tôi cần lưu trong một Agiá trị biến twoBgiá trị biến fourtừ chuỗi trên

Câu trả lời:


106

Sử dụng cutvới _dấu phân cách trường và nhận các trường mong muốn:

A="$(cut -d'_' -f2 <<<'one_two_three_four_five')"
B="$(cut -d'_' -f4 <<<'one_two_three_four_five')"

Bạn cũng có thể sử dụng echovà đường ống thay vì chuỗi Here:

A="$(echo 'one_two_three_four_five' | cut -d'_' -f2)"
B="$(echo 'one_two_three_four_five' | cut -d'_' -f4)"

Thí dụ:

$ s='one_two_three_four_five'

$ A="$(cut -d'_' -f2 <<<"$s")"
$ echo "$A"
two

$ B="$(cut -d'_' -f4 <<<"$s")"
$ echo "$B"
four

Có sự thay thế nào không? Tôi đang sử dụng ksh (không phải bsh) và nó trả về ksh: lỗi cú pháp: `<'không mong muốn
Alex

@Alex Kiểm tra các chỉnh sửa của tôi.
heemayl

Câu trả lời hay, tôi có một câu hỏi nhỏ: điều gì xảy ra nếu biến "$ s" của bạn là một thư mục đường dẫn. Khi tôi cố gắng cắt một thư mục đường dẫn, tôi sẽ làm như sau: `$ FILE = my_user / my_folder / [file] *` $ echo $FILE my_user/my_folder/file.csv $ A="$(cut -d'/' -f2 <<<"$FILE")" $ echo $A [file]* Bạn có biết chuyện gì đang xảy ra ở đây không?
Henry Navarro

1
Và nếu bạn chỉ muốn trường cuối cùng, chỉ sử dụng các nội dung shell - mà không cần chỉ định vị trí của nó hoặc khi bạn không biết số lượng các trường:echo "${s##*_}"
Amit N Nikol

19

Chỉ sử dụng các cấu trúc sh POSIX, bạn có thể sử dụng các cấu trúc thay thế tham số để phân tích một dấu phân cách tại một thời điểm. Lưu ý rằng mã này giả định rằng có số lượng các trường cần thiết, nếu không thì trường cuối cùng được lặp lại.

string='one_two_three_four_five'
remainder="$string"
first="${remainder%%_*}"; remainder="${remainder#*_}"
second="${remainder%%_*}"; remainder="${remainder#*_}"
third="${remainder%%_*}"; remainder="${remainder#*_}"
fourth="${remainder%%_*}"; remainder="${remainder#*_}"

Ngoài ra, bạn có thể sử dụng thay thế tham số không được trích dẫn với mở rộng ký tự đại diện bị vô hiệu hóa và IFSđược đặt thành ký tự phân cách (điều này chỉ hoạt động nếu dấu phân cách là một ký tự không phải khoảng trắng hoặc nếu bất kỳ chuỗi khoảng trắng nào là dấu phân cách).

string='one_two_three_four_five'
set -f; IFS='_'
set -- $string
second=$2; fourth=$4
set +f; unset IFS

Điều này làm tắc nghẽn các tham số vị trí. Nếu bạn làm điều này trong một hàm, chỉ các tham số vị trí của hàm bị ảnh hưởng.

Tuy nhiên, một cách tiếp cận khác là sử dụng readnội dung.

IFS=_ read -r first second third fourth trail <<'EOF'
one_two_three_four_five
EOF

Việc sử dụng unset IFSkhông trở về IFSmặc định. Nếu sau đó ai đó OldIFS="$IFS"sẽ có một giá trị null bên trong OldIFS. Ngoài ra, giả định rằng giá trị trước đó của IFS là mặc định, điều này rất có thể (và hữu ích) là không thể. Giải pháp đúng duy nhất là lưu trữ trong old="$IFS"và sau đó khôi phục bằng IFS = "$ old". Hoặc ... sử dụng vỏ phụ (...). Hoặc, tốt hơn, đọc câu trả lời của tôi.
sorontar

@sorontar unset IFSkhông khôi phục IFSvề giá trị mặc định, nhưng nó trả về trường tách thành hiệu ứng mặc định. Vâng, đó là một hạn chế, nhưng thường là một chấp nhận được trong thực tế. Vấn đề với một subshell là chúng ta cần lấy dữ liệu từ nó. Tôi cho thấy một giải pháp không thay đổi trạng thái ở cuối, với read. (Nó hoạt động trong vỏ POSIX, nhưng IIRC không có trong vỏ Bourne bởi vì nó sẽ chạy readtrong một subshell do đây-tài liệu.) Sử dụng <<<như trong bạn trả lời là một biến thể mà chỉ hoạt động trong ksh / bash / zsh.
Gilles

Tôi không thấy một vấn đề ngay cả với vỏ att hoặc gia truyền về một lớp vỏ. Tất cả các vỏ được thử nghiệm (bao gồm cả bourne cũ) cung cấp giá trị chính xác trong vỏ chính.
sorontar

Điều gì xảy ra nếu con đường của tôi là một cái gì đó như thế user/my_folder/[this_is_my_file]*nào? Những gì tôi có được khi thực hiện theo các bước này là[this_is_my_file]*
Henry Navarro

@HenryNavarro Đầu ra này không tương ứng với bất kỳ đoạn mã nào trong câu trả lời của tôi. Không ai trong số họ làm bất cứ điều gì đặc biệt /.
Gilles

17

Muốn xem awkcâu trả lời, vì vậy đây là một:

A=$(awk -F_ '{print $2}' <<< 'one_two_three_four_five')
B=$(awk -F_ '{print $4}' <<< 'one_two_three_four_five')

1
Và nếu bạn muốn mảnh cuối cùng - mà không cần chỉ định vị trí của nó hoặc khi bạn không biết số lượng trường:awk -F_ '{print $NF}' <<< 'one_two_3_4_five'
Amit N Nikol

8

Cách đơn giản nhất (đối với hệ vỏ có <<<) là:

 IFS='_' read -r a second a fourth a <<<"$string"

Sử dụng một biến thời gian $athay $_vì bởi vì một vỏ phàn nàn.

Trong một kịch bản đầy đủ:

 string='one_two_three_four_five'
 IFS='_' read -r a second a fourth a <<<"$string"
 echo "$second $fourth"

Không thay đổi IFS, không xảy ra sự cố với set -f(Mở rộng tên đường dẫn) Không thay đổi các tham số vị trí ("$ @").


Đối với một giải pháp di động cho tất cả các hệ vỏ (có, bao gồm tất cả POSIX) mà không thay đổi IFS hoặc set -f, sử dụng tương đương (phức tạp hơn một chút):

string='one_two_three_four_five'

IFS='_' read -r a second a fourth a <<-_EOF_
$string
_EOF_

echo "$second $fourth"

Hiểu rằng các giải pháp này (cả tài liệu ở đây và việc sử dụng <<<sẽ loại bỏ tất cả các dòng mới.
Và nó được thiết kế theo nội dung biến "một lớp".
Các giải pháp cho nhiều lớp có thể nhưng cần các cấu trúc phức tạp hơn.


Một giải pháp rất đơn giản có thể có trong phiên bản bash 4.4

readarray -d _ -t arr <<<"$string"

echo "array ${arr[1]} ${arr[3]}"   # array numbers are zero based.

Không có vỏ tương đương với POSIX, vì nhiều vỏ POSIX không có mảng.

Đối với hệ vỏ có mảng có thể đơn giản như:
(đã thử nghiệm làm việc trong attsh, lksh, mksh, ksh và bash)

set -f; IFS=_; arr=($string)

Nhưng với rất nhiều hệ thống ống nước bổ sung để giữ và thiết lập lại các biến và tùy chọn:

string='one_* *_three_four_five'

case $- in
    *f*) noglobset=true; ;;
    *) noglobset=false;;
esac

oldIFS="$IFS"

set -f; IFS=_; arr=($string)

if $noglobset; then set -f; else set +f; fi

echo "two=${arr[1]} four=${arr[3]}"

Trong zsh, các mảng bắt đầu bằng 1 và không phân tách chuỗi theo mặc định.
Vì vậy, một số thay đổi cần phải được thực hiện để làm việc này trong zsh.


các giải pháp sử dụng read rất đơn giản miễn là OP không muốn trích xuất các phần tử thứ 76 và 127 từ một chuỗi dài ...
don_crissti

@don_crissti Vâng, tất nhiên, nhưng một cấu trúc tương tự: readarraycó thể dễ sử dụng hơn cho tình huống đó.
sorontar

@don_crissti Tôi cũng đã thêm một giải pháp mảng cho các shell có mảng. Đối với hệ vỏ POSIX, tốt, không có mảng, tham số vị trí lên tới 127 phần tử không phải là giải pháp "đơn giản" theo bất kỳ biện pháp nào.
sorontar

2

Với zshbạn có thể chia chuỗi (bật _) thành một mảng:

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

và sau đó truy cập từng / bất kỳ phần tử nào thông qua chỉ mục mảng:

print -r ${elements[4]}

Hãy nhớ rằng trong zsh(không giống ksh/ bash) các chỉ số mảng bắt đầu từ 1 .


Hãy nhớ để thêm set -fcảnh báo cho giải pháp đầu tiên. ... dấu hoa thị *có lẽ?
sorontar

@sorontar - Tại sao bạn nghĩ tôi cần set -f? Tôi không sử dụng read/ IFS. Hãy thử các giải pháp của tôi với một chuỗi như *_*_*hoặc bất cứ điều gì ...
don_crissti

Không phải cho zsh, nhưng người dùng đã yêu cầu một giải pháp ksh, vì vậy, anh ta có thể thử sử dụng nó trong vỏ đó. Một cảnh báo sẽ giúp anh ta tránh được vấn đề.
sorontar

1

Là một giải pháp python được phép?

# python -c "import sys; print sys.argv[1].split('_')[1]" one_two_three_four_five
two

# python -c "import sys; print sys.argv[1].split('_')[3]" one_two_three_four_five
four

Không có câu trả lời tồi tệ
Raj Kumar

0

Một ví dụ khác về awk; hiểu đơn giản hơn

A=\`echo one_two_three_four_five | awk -F_ '{print $1}'\`  
B=\`echo one_two_three_four_five | awk -F_ '{print $2}'\`  
C=\`echo one_two_three_four_five | awk -F_ '{print $3}'\`  
... and so on...  

Có thể được sử dụng với các biến cũng.
Giả sử:
this_str = "one_two_three_four_five"
Sau đó, các công việc sau:
A = `echo $ {this_str} | awk -F_ '{in $ 1}' `
B =` echo $ {this_str} | awk -F_ '{in $ 2}' `
C =` echo $ {this_str} | awk -F_ '{in $ 3}' `
... và cứ thế ...

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.