Pisahkan string menggunakan IFS

8

Saya telah menulis skrip sampel untuk memisahkan string tetapi tidak berfungsi seperti yang diharapkan

#!/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

keluaran

One
XX
X
17.0.0
17 0 0

tapi saya berharap hasilnya:

      One
      XX
      X
      17.0.0
      17
      0
      0
pengguna112232
sumber
Ngomong-ngomong, jika salah satu jawaban di bawah ini menyelesaikan masalah Anda, mohon luangkan waktu dan terima dengan mengklik tanda centang di sebelah kiri. Itu akan menandai pertanyaan sebagai dijawab dan cara terima kasih diungkapkan di situs Stack Exchange.
terdon

Jawaban:

2

Perbaiki, (lihat juga jawaban S. Chazelas untuk latar belakang), dengan output yang masuk akal:

#!/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

Keluaran:

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

Catatan:

  • Lebih baik untuk menempatkan bersyarat 2 lingkaran di dalam 1 lingkaran.

  • bashsubstitusi pola ( "${i//.}") memeriksa apakah ada .elemen. ( casePernyataan mungkin lebih sederhana, meskipun kurang mirip dengan kode OP .)

  • reading $arraydengan memasukkan <<< "${ADDR[3]}"kurang umum daripada <<< "$i". Ini menghindari perlu tahu elemen mana yang memiliki .s.

  • Kode ini mengasumsikan bahwa pencetakan " Elemen: 17.0.0 " tidak disengaja. Jika Perilaku itu dimaksudkan, ganti loop utama dengan:

    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
agc
sumber
1
case $i in (*.*) ...akan menjadi cara yang lebih kanonik untuk memeriksa yang $iberisi .(dan juga portabel untuk sh). Jika Anda menyukai kshisme, lihat juga:[[ $i = *.* ]]
Stéphane Chazelas
@ StéphaneChazelas, Sudah disebutkan casedalam catatan di akhir, tapi kami setuju. (Karena OP menggunakan keduanya <<<dan array , ini bukan shpertanyaan besar.)
agc
10

Di versi lama bashAnda harus mengutip variabel setelahnya <<<. Itu diperbaiki di 4.4. Dalam versi yang lebih lama, variabel akan dipecah pada IFS dan kata-kata yang dihasilkan bergabung di ruang sebelum disimpan dalam file sementara yang membentuk <<<pengalihan itu.

Di 4.2 dan sebelumnya, ketika mengarahkan ulang builtin seperti readatau command, pemisahan itu bahkan akan mengambil IFS untuk builtin itu (4.3 memperbaiki itu):

$ 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

Yang diperbaiki di 4.3:

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

Namun $amasih ada pemisahan kata di sana:

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

Dalam 4.4:

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

Untuk portabilitas ke versi yang lebih lama, kutip variabel Anda (atau gunakan dari zshmana <<<asalnya dan yang tidak memiliki masalah itu)

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

Perhatikan bahwa pendekatan untuk memecah string hanya berfungsi untuk string yang tidak mengandung karakter baris baru. Juga mencatat bahwa a..b.c.akan terpecah menjadi "a", "", "b", "c"(tidak ada mengosongkan elemen terakhir).

Untuk membagi string sewenang-wenang, Anda dapat menggunakan operator split + glob sebagai gantinya (yang akan menjadikannya standar dan menghindari menyimpan konten variabel dalam file temp seperti <<<halnya):

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

atau:

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

The ''adalah untuk melestarikan elemen kosong tertinggal jika ada. Itu juga akan membagi kosong $varmenjadi satu elemen kosong.

Atau gunakan shell dengan operator pemisahan yang tepat:

  • 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
Stéphane Chazelas
sumber
1

Dengan awk akan dikenakan biaya satu baris:

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'[-.]'- pemisah bidang berdasarkan beberapa karakter, dalam kasus kami -dan.

Hasil:

Element : One
Element : XX
Element : X
Num : 17
Num : 0
Num : 0
RomanPerekhrest
sumber
Hal yang sama dapat dilakukan denganIFS=-. read -r a array <<< "$IN"
Stéphane Chazelas
@ StéphaneChazelas, ini berbeda. Anda hanya menunjukkan langkah dengan mengubah string menjadi array. Tapi satu-baris saya didedikasikan untuk mencakup semua: membelah bidang, pemrosesan dan keluaran. Saya tidak bersaing dengan jawaban Anda, mereka hanya berbeda
RomanPerekhrest
0

Di sini cara saya:

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

hasil seperti yang diharapkan:

Num:17
Num:0
Num:0
tonioc
sumber
Itu $INmemanggil operator + gumpal split. Di sini, Anda tidak ingin bagian glob (coba IN=*-*-/*-17.0.0misalnya), jadi Anda ingin melakukannya set -o noglobsebelum memintanya. Lihat jawaban saya untuk detailnya.
Stéphane Chazelas
1
Secara umum, cobalah untuk menghindari "menyimpan" IFSdan mengaturnya secara global. Anda benar-benar hanya ingin mengubah nilai IFSkapan $INdiperluas, dan Anda juga tidak ingin ekspansi pathname dilakukan pada ekspansi. Lebih jauh, OIFS=$IFStidak membedakan antara case ketika IFSdiatur ke string kosong, dan kapan IFSbenar-benar tidak disetel.
chepner