Pisahkan string dengan pembatas dan dapatkan elemen ke-N


75

Saya punya string:

one_two_three_four_five

Saya perlu menyimpan Anilai variabel twodan Bnilai variabel fourdari string di atas

Jawaban:


107

Gunakan cutdengan _sebagai pembatas bidang dan dapatkan bidang yang diinginkan:

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

Anda juga bisa menggunakan echodan mem-pipe bukannya string Here:

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

Contoh:

$ s='one_two_three_four_five'

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

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

Apakah ada alternatif lain? Saya menggunakan ksh (bukan bsh) dan mengembalikan ksh: kesalahan sintaks: `<'tak terduga
Alex

@Alex Periksa hasil edit saya.
heemayl

Jawaban yang bagus, saya punya sedikit pertanyaan: apa yang terjadi jika variabel Anda "$ s" adalah folder path. Ketika saya mencoba memotong folder path, saya melakukan hal-hal berikut: `$ FILE = my_user / my_folder / [file] *` $ echo $FILE my_user/my_folder/file.csv $ A="$(cut -d'/' -f2 <<<"$FILE")" $ echo $A [file]* Apakah Anda tahu apa yang terjadi di sini?
Henry Navarro

1
Dan jika Anda hanya ingin bidang terakhir, gunakan hanya shell builtin - tanpa perlu menentukan posisinya, atau ketika Anda tidak tahu jumlah bidang:echo "${s##*_}"
Amit Naidu

19

Hanya menggunakan konstruksi sh POSIX, Anda bisa menggunakan konstruksi substitusi parameter untuk mengurai satu pembatas pada satu waktu. Perhatikan bahwa kode ini mengasumsikan bahwa ada jumlah bidang yang diperlukan, jika tidak, bidang terakhir diulang.

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

Atau, Anda dapat menggunakan substitusi parameter yang tidak dikutip dengan ekspansi wildcard dinonaktifkan dan IFSdiatur ke karakter pembatas (ini hanya bekerja jika pembatas adalah karakter non-spasi tunggal atau jika urutan spasi apa pun adalah pembatas).

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

Ini mengacaukan parameter posisi. Jika Anda melakukan ini dalam suatu fungsi, hanya parameter posisi fungsi yang terpengaruh.

Namun pendekatan lain adalah dengan menggunakan readbuiltin.

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

Penggunaan unset IFStidak kembali IFSke default. Jika setelah itu seseorang OldIFS="$IFS"akan memiliki nilai nol di dalam OldIFS. Juga, diasumsikan bahwa nilai IFS sebelumnya adalah default, yang sangat mungkin (dan berguna) untuk tidak. Satu-satunya solusi yang benar adalah menyimpan old="$IFS"dan mengembalikan dengan IFS = "$ old". Atau ... gunakan sub-shell (...). Atau, lebih baik lagi, baca jawaban saya.
sorontar

@sorontar unset IFStidak mengembalikan IFSke nilai default, tetapi mengembalikan pemisahan bidang ke efek default. Ya, ini adalah batasan, tetapi biasanya yang dapat diterima dalam praktik. Masalah dengan subkulit adalah kita perlu mengambil data darinya. Saya memang menunjukkan solusi yang tidak mengubah keadaan pada akhirnya, dengan read. (Ini bekerja di shell POSIX, tetapi IIRC tidak di shell Bourne karena ia akan menjalankan readdalam subkulit karena dokumen di sini.) Menggunakan <<<seperti pada jawaban Anda adalah varian yang hanya bekerja di ksh / bash / zsh.
Gilles

Saya tidak melihat masalah bahkan dengan shell att atau heirloom tentang subkulit. Semua cangkang yang diuji (termasuk bourne yang lama) memberikan nilai yang benar di cangkang utama.
sorontar

Apa yang terjadi jika jalan saya seperti ini user/my_folder/[this_is_my_file]*? Apa yang saya dapatkan ketika saya mengikuti langkah-langkah ini adalah[this_is_my_file]*
Henry Navarro

@HenryNavarro Output ini tidak sesuai dengan salah satu potongan kode dalam jawaban saya. Tak satu pun dari mereka melakukan sesuatu yang istimewa /.
Gilles

17

Ingin melihat awkjawaban, jadi ini satu:

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

1
Dan jika Anda ingin bagian terakhir - tanpa perlu menentukan posisinya atau ketika Anda tidak tahu jumlah bidang:awk -F_ '{print $NF}' <<< 'one_two_3_4_five'
Amit Naidu

8

Cara paling sederhana (untuk kerang dengan <<<) adalah:

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

Menggunakan variabel temporal $aalih-alih $_karena satu shell mengeluh.

Dalam skrip lengkap:

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

Tidak ada perubahan IFS, bukan masalah dengan set -f(Perluasan pathname) Tidak ada perubahan pada parameter posisi ("$ @").


Untuk solusi portabel untuk semua shell (ya, semua POSIX termasuk) tanpa mengubah IFS atau set -f, gunakan (setara dengan sedikit lebih kompleks) heredoc:

string='one_two_three_four_five'

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

echo "$second $fourth"

Memahami bahwa solusi ini (baik di sini-doc dan penggunaan <<<akan menghapus semua baris baru.
Dan bahwa ini dirancang untuk konten variabel "satu liner."
Solusi untuk multi-liner dimungkinkan tetapi membutuhkan konstruksi yang lebih kompleks.


Solusi yang sangat sederhana dimungkinkan di versi bash 4.4

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

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

Tidak ada padanan untuk shell POSIX, karena banyak shell POSIX tidak memiliki array.

Untuk cangkang yang memiliki larik mungkin sesederhana:
(diuji bekerja di attsh, lksh, mksh, ksh, dan bash)

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

Tetapi dengan banyak pipa tambahan untuk menjaga dan mengatur ulang variabel dan opsi:

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]}"

Di zsh, array dimulai pada 1, dan tidak memisah string secara default.
Jadi beberapa perubahan perlu dilakukan untuk mendapatkan ini berfungsi di zsh.


solusi yang digunakan read sederhana selama OP tidak ingin mengekstraksi elemen ke-76 dan ke-127 dari string panjang ...
don_crissti

@don_crissti Yah, ya, tentu saja, tetapi konstruksi yang serupa: readarraybisa lebih mudah digunakan untuk situasi itu.
sorontar

@don_crissti Saya juga menambahkan solusi array untuk shell yang memiliki array. Untuk cangkang POSIX, well, tidak memiliki array, parameter posisi hingga 127 elemen bukanlah solusi "sederhana" dengan ukuran apa pun.
sorontar

2

Dengan zshAnda dapat membagi string (on _) menjadi array:

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

dan kemudian mengakses setiap elemen melalui indeks array:

print -r ${elements[4]}

Perlu diingat bahwa dalam indeks arrayzsh (tidak seperti ksh/ bash) mulai dari 1 .


Harap ingat untuk menambahkan set -fperingatan ke solusi pertama. ... tanda bintang *mungkin?
sorontar

@sorontar - mengapa Anda pikir saya perlu set -f? Saya tidak menggunakan read/ IFS. Coba solusi saya dengan string seperti *_*_*atau apa pun ...
don_crissti

Bukan untuk zsh, tetapi pengguna meminta solusi ksh, jadi, ia mungkin mencoba menggunakannya di shell itu. Peringatan akan membantunya menghindari masalah.
sorontar

1

Apakah solusi python diizinkan?

# 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

Tidak, jawaban buruk buruk
Raj Kumar

0

Contoh awk lainnya; lebih mudah dimengerti.

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...  

Dapat digunakan dengan variabel juga.
Misalkan:
this_str = "one_two_three_four_five"
Maka yang berikut berfungsi:
A = `echo $ {this_str} | awk -F_ '{print $ 1}' `
B =` echo $ {this_str} | awk -F_ '{print $ 2}' `
C =` echo $ {this_str} | awk -F_ '{print $ 3}' `
... dan seterusnya ...

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.