Chia chuỗi bằng IFS


8

Tôi đã viết một tập lệnh mẫu để phân tách chuỗi nhưng nó không hoạt động như mong đợi

#!/bin/bash
IN="One-XX-X-17.0.0"
IFS='-' read -r -a ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
 echo "Element:$i"
done
#split 17.0.0 into NUM
IFS='.' read -a array <<<${ADDR[3]};
for element in "${array[@]}"
do
    echo "Num:$element"
done

đầu ra

One
XX
X
17.0.0
17 0 0

nhưng tôi dự kiến ​​đầu ra là:

      One
      XX
      X
      17.0.0
      17
      0
      0

Nhân tiện, nếu một trong những câu trả lời dưới đây giải quyết được vấn đề của bạn, vui lòng dành chút thời gian và chấp nhận nó bằng cách nhấp vào dấu kiểm bên trái. Điều đó sẽ đánh dấu câu hỏi đã được trả lời và là cách cảm ơn được thể hiện trên các trang web Stack Exchange.
terdon

Câu trả lời:


2

Khắc phục, (xem thêm câu trả lời của S. Chazelas cho nền), với đầu ra hợp lý:

#!/bin/bash
IN="One-XX-X-17.0.0"
IFS='-' read -r -a ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
    if [ "$i" = "${i//.}" ] ; then 
        echo "Element:$i" 
        continue
    fi
    # split 17.0.0 into NUM
    IFS='.' read -a array <<< "$i"
    for element in "${array[@]}" ; do
        echo "Num:$element"
    done
done

Đầu ra:

Element:One
Element:XX
Element:X
Num:17
Num:0
Num:0

Ghi chú:

  • Nó tốt hơn để đặt điều kiện thứ 2 vòng lặp trong các ngày 1 vòng lặp.

  • bashthay thế mẫu ( "${i//.}") kiểm tra nếu có .một phần tử. (Một casetuyên bố có thể đơn giản hơn, mặc dù ít giống với mã của OP .)

  • reading $arraybằng cách nhập vào <<< "${ADDR[3]}"là ít chung hơn <<< "$i". Nó tránh được cần phải biết yếu tố có .s.

  • Mã này giả định rằng việc in " Element: 17.0.0 " là vô ý. Nếu hành vi đó được dự định, thay thế vòng lặp chính bằng:

    for i in "${ADDR[@]}"; do
       echo "Element:$i" 
       if [ "$i" != "${i//.}" ] ; then 
       # split 17.0.0 into NUM
           IFS='.' read -a array <<< "$i"
           for element in "${array[@]}" ; do
               echo "Num:$element"
           done
       fi
    done
    

1
case $i in (*.*) ...sẽ là một cách hợp quy hơn để kiểm tra $icó chứa .(và cũng có thể mang theo sh). Nếu bạn thích kshism, xem thêm:[[ $i = *.* ]]
Stéphane Chazelas

@ StéphaneChazelas, Đã được đề cập casetrong ghi chú ở cuối, nhưng chúng tôi đồng ý. (Vì OP sử dụng cả hai <<<mảng , nên đây không phải là shcâu hỏi nhiều.)
agc

10

Trong các phiên bản cũ của bashbạn đã phải trích dẫn các biến sau <<<. Điều đó đã được sửa trong 4.4. Trong các phiên bản cũ hơn, biến sẽ được phân tách trên IFS và các từ kết quả được nối trên không gian trước khi được lưu trữ trong tệp tạm thời tạo nên sự <<<chuyển hướng đó .

Trong 4.2 trở về trước, khi chuyển hướng các nội dung như readhoặc command, việc chia tách đó thậm chí sẽ lấy IFS cho nội dung đó (4.3 đã sửa lỗi đó):

$ bash-4.2 -c 'a=a.b.c.d; IFS=. read x <<< $a; echo  "$x"'
a b c d
$ bash-4.2 -c 'a=a.b.c.d; IFS=. cat <<< $a'
a.b.c.d
$ bash-4.2 -c 'a=a.b.c.d; IFS=. command cat <<< $a'
a b c d

Cái đó đã được sửa trong 4.3:

$ bash-4.3 -c 'a=a.b.c.d; IFS=. read x <<< $a; echo  "$x"'
a.b.c.d

Nhưng $avẫn phải chịu sự chia tách từ đó:

$ bash-4.3 -c 'a=a.b.c.d; IFS=.; read x <<< $a; echo  "$x"'
a b c d

Trong 4,4:

$ bash-4.4 -c 'a=a.b.c.d; IFS=.; read x <<< $a; echo  "$x"'
a.b.c.d

Đối với tính di động đối với các phiên bản cũ hơn, hãy trích dẫn biến của bạn (hoặc sử dụng từ zshđâu <<<đến từ nơi đầu tiên và điều đó không có vấn đề đó)

$ bash-any-version -c 'a=a.b.c.d; IFS=.; read x <<< "$a"; echo "$x"'
a.b.c.d

Lưu ý rằng cách tiếp cận để phân tách một chuỗi chỉ hoạt động đối với các chuỗi không chứa các ký tự dòng mới. Cũng lưu ý rằng a..b.c.sẽ chia thành "a", "", "b", "c"(không có sản phẩm nào yếu tố cuối cùng).

Để phân tách các chuỗi tùy ý, bạn có thể sử dụng toán tử split + global thay thế (điều này sẽ làm cho nó trở thành tiêu chuẩn và tránh lưu trữ nội dung của một biến trong tệp tạm thời như sau <<<):

var='a.new
line..b.c.'
set -o noglob # disable glob
IFS=.
set -- $var'' # split+glob
for i do
  printf 'item: <%s>\n' "$i"
done

hoặc là:

array=($var'') # in shells with array support

Đây ''là để bảo tồn một yếu tố trống rỗng nếu có. Điều đó cũng sẽ chia một phần trống $varthành một phần tử trống.

Hoặc sử dụng shell với toán tử tách thích hợp:

  • zsh:

    array=(${(s:.:)var} # removes empty elements
    array=("${(@s:.:)var}") # preserves empty elements
    
  • rc:

    array = ``(.){printf %s $var} # removes empty elements
  • fish

    set array (string split . -- $var) # not for multiline $var

1

Với awk, bạn sẽ mất một dòng:

IN="One-XX-X-17.0.0"

awk -F'[-.]' '{ for(i=1;i<=NF;i++) printf "%s : %s\n",($i~/^[0-9]+$/?"Num":"Element"),$i }' <<<"$IN"
  • -F'[-.]'- dấu tách trường dựa trên nhiều ký tự, trong trường hợp của chúng tôi -.

Đầu ra:

Element : One
Element : XX
Element : X
Num : 17
Num : 0
Num : 0

Điều tương tự cũng có thể được thực hiện vớiIFS=-. read -r a array <<< "$IN"
Stéphane Chazelas

@ StéphaneChazelas, nó khác. Bạn đang hiển thị chỉ bước với chuyển đổi một chuỗi thành mảng. Nhưng một dòng của tôi được dành riêng để bao gồm tất cả: chia thành các trường, xử lý và xuất ra. Tôi không cạnh tranh với câu trả lời của bạn, chúng chỉ khác nhau
RomanPerekhrest

0

Theo cách của tôi:

OIFS=$IFS
IFS='-'
IN="One-XX-X-17.0.0"
ADDR=($IN)
for i in "${ADDR[@]}"; do
 echo "Element:$i"
done
IFS='.'
array=(${ADDR[3]})
for element in "${array[@]}"
do
  echo "Num:$element"
done

kết quả như mong đợi:

Num:17
Num:0
Num:0

Đó $INlà gọi toán tử split + global. Ở đây, bạn không muốn phần toàn cầu ( IN=*-*-/*-17.0.0ví dụ thử ), vì vậy bạn muốn làm set -o noglobtrước khi gọi nó. Xem câu trả lời của tôi để biết chi tiết.
Stéphane Chazelas

1
Nói chung, cố gắng tránh "tiết kiệm" IFSvà thiết lập nó trên toàn cầu. Bạn thực sự chỉ muốn thay đổi giá trị của IFSkhi $INđược mở rộng và bạn cũng không muốn mở rộng tên đường dẫn được thực hiện trên bản mở rộng. Hơn nữa, OIFS=$IFSkhông phân biệt giữa các trường hợp khi IFSđược đặt thành một chuỗi trống và khi nào IFShoàn toàn không được đặt.
chepner
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.