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 -f
atau 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 -d
untuk readarray).
SH
Versi shell tidak dapat menggunakan array, satu-satunya array yang tersedia adalah parameter posisi.
Menggunakan tr -s
hanya 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 command
tidak valid di Bourne.
Di zsh, command
panggilan hanya perintah eksternal dan membuat eval gagal jika command
digunakan.
Di ksh, bahkan dengan command
, nilai IFS diubah dalam lingkup global.
Dan command
membuat pemecahan gagal dalam shell terkait mksh (mksh, lksh, posh) Menghapus perintah command
membuat kode dijalankan pada lebih banyak shell. Tetapi: menghapus command
akan 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 command
kata jika tidak di emulasi sh / POSIX. Perintah akan gagal di ksh93 (IFS menyimpan nilai yang diubah). Di zsh perintah command
membuat zsh mencoba mencari eval
sebagai 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 command
dan 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.