Jawaban sederhananya adalah: tutup semua pembatas menjadi satu (yang pertama).
Itu membutuhkan loop (yang berjalan kurang dari log(N)kali):
var=':a bc::d ef:#$%_+$$% ^%&*(*&*^
$#,.::ghi::*::' # a long test string.
d=':@!#$%^&*()_+,.' # delimiter set
f=${d:0:1} # first delimiter
v=${var//["$d"]/"$f"}; # convert all delimiters to
: # the first of the delimiter set.
tmp=$v # temporal variable (v).
while
tmp=${tmp//["$f"]["$f"]/"$f"}; # collapse each two delimiters to one
[[ "$tmp" != "$v" ]]; # If there was a change
do
v=$tmp; # actualize the value of the string.
done
Yang harus dilakukan adalah memisahkan string dengan benar pada satu pembatas, dan mencetaknya:
readarray -td "$f" arr < <(printf '%s%s' "$v"'' "$f")
printf '<%s>' "${arr[@]}" ; echo
Tidak perlu set -fatau untuk mengubah IFS.
Diuji dengan spasi, baris baru, dan karakter glob. Semua bekerja Cukup lambat (seperti lingkaran shell seharusnya diharapkan).
Tetapi hanya untuk bash (bash 4.4+ karena opsi -duntuk readarray).
SH
Versi shell tidak dapat menggunakan array, satu-satunya array yang tersedia adalah parameter posisi.
Menggunakan tr -shanya satu baris (IFS tidak berubah dalam skrip):
set -f; IFS=$f command eval set -- '$(echo "$var" | tr -s "$d" "[$f*]" )""'
Dan cetak:
printf '<%s>' "$@" ; echo
Masih lambat, tapi tidak lebih.
Perintah commandtidak valid di Bourne.
Di zsh, commandpanggilan hanya perintah eksternal dan membuat eval gagal jika commanddigunakan.
Di ksh, bahkan dengan command, nilai IFS diubah dalam lingkup global.
Dan commandmembuat pemecahan gagal dalam shell terkait mksh (mksh, lksh, posh) Menghapus perintah commandmembuat kode dijalankan pada lebih banyak shell. Tetapi: menghapus commandakan membuat IFS mempertahankan nilainya di sebagian besar shell (eval adalah builtin khusus) kecuali dalam bash (tanpa mode posix) dan zsh dalam mode default (tanpa emulasi). Konsep ini tidak dapat dibuat berfungsi di zsh default baik dengan atau tanpa command.
Beberapa karakter IFS
Ya, IFS bisa multi karakter, tetapi setiap karakter akan menghasilkan satu argumen:
set -f; IFS="$d" command eval set -- '$(echo "$var" )""'
printf '<%s>' "$@" ; echo
Akan menghasilkan:
<><a bc><><d ef><><><><><><><><>< ><><><><><><><><><
><><><><><><ghi><><><><><>
Dengan bash, Anda dapat menghilangkan commandkata jika tidak di emulasi sh / POSIX. Perintah akan gagal di ksh93 (IFS menyimpan nilai yang diubah). Di zsh perintah commandmembuat zsh mencoba mencari evalsebagai perintah eksternal (yang tidak ditemukan) dan gagal.
Apa yang terjadi adalah bahwa satu-satunya karakter IFS yang secara otomatis diciutkan ke satu pembatas adalah ruang putih IFS.
Satu ruang di IFS akan menciutkan semua ruang berurutan menjadi satu. Satu tab akan menciutkan semua tab. Satu spasi dan satu tab akan menciutkan run spasi dan / atau tab menjadi satu pembatas. Ulangi ide dengan baris baru.
Untuk meruntuhkan beberapa pembatas beberapa juggling diperlukan.
Dengan asumsi ASCII 3 (0x03) tidak digunakan dalam input var:
var=${var// /$'\3'} # protect spaces
var=${var//["$d"]/ } # convert all delimiters to spaces
set -f; # avoid expanding globs.
IFS=" " command eval set -- '""$var""' # split on spaces.
set -- "${@//$'\3'/ }" # convert spaces back.
Sebagian besar komentar tentang ksh, zsh dan bash (about commanddan IFS) masih berlaku di sini.
Nilai $'\0'akan kurang mungkin dalam input teks, tetapi variabel bash tidak dapat berisi NUL ( 0x00).
Tidak ada perintah internal di sh untuk melakukan operasi string yang sama, jadi tr adalah satu-satunya solusi untuk skrip sh.