Parsing array menggunakan IFS dengan nilai ruang non-putih menciptakan elemen kosong.
Bahkan menggunakan tr -s
untuk mengecilkan beberapa batasan ke satu batasan saja tidak cukup.
Sebuah contoh dapat menjelaskan masalah ini dengan lebih jelas ..
Apakah ada cara untuk mencapai hasil "normal" melalui tweaking IFS (apakah ada pengaturan yang terkait untuk mengubah perilaku IFS? .... mis. Untuk bertindak sama seperti ruang kosong standar IFS.
var=" abc def ghi "
echo "============== IFS=<default>"
arr=($var)
for x in ${!arr[*]} ; do
echo "# arr[$x] \"${arr[x]}\""
done
#
sfi="$IFS" ; IFS=':'
set -f # Disable file name generation (globbing)
# (This data won't "glob", but unless globbing
# is actually needed, turn if off, because
# unusual/unexpected combinations of data can glob!
# and they can do it in the most obscure ways...
# With IFS, "you're not in Kansas any more! :)
var=":abc::def:::ghi::::"
echo "============== IFS=$IFS"
arr=($var)
for x in ${!arr[*]} ; do
echo "# arr[$x] \"${arr[x]}\""
done
echo "============== IFS=$IFS and tr"
arr=($(echo -n "$var"|tr -s "$IFS"))
for x in ${!arr[*]} ; do
echo "# arr[$x] \"${arr[x]}\""
done
set +f # enable globbing
IFS="$sfi" # re-instate original IFS val
echo "============== IFS=<default>"
Ini outputnya
============== IFS=<default>
# arr[0] "abc"
# arr[1] "def"
# arr[2] "ghi"
============== IFS=:
# arr[0] ""
# arr[1] "abc"
# arr[2] ""
# arr[3] "def"
# arr[4] ""
# arr[5] ""
# arr[6] "ghi"
# arr[7] ""
# arr[8] ""
# arr[9] ""
============== IFS=: and tr
# arr[0] ""
# arr[1] "abc"
# arr[2] "def"
# arr[3] "ghi"
============== IFS=<default>
Jawaban:
Untuk menghapus beberapa karakter pembatas berturut-turut (non-spasi), dua (string / array) ekspansi parameter dapat digunakan. Caranya adalah dengan mengatur
IFS
variabel ke string kosong untuk ekspansi parameter array.Ini didokumentasikan di
man bash
bawah Pemisahan Kata :sumber
IFS=' '
(yaitu spasi) berperilaku sama. Saya menemukan ini kurang membingungkan daripada argumen nol eksplisit ("" atau '') dariIFS
.Dari
bash
halaman manual:Ini berarti bahwa spasi putih IFS (spasi, tab dan baris baru) tidak diperlakukan seperti pemisah lainnya. Jika Anda ingin mendapatkan perilaku yang persis sama dengan pemisah alternatif, Anda dapat melakukan swapping pemisah dengan bantuan
tr
ataused
:The
%#%#%#%#%
hal adalah nilai sihir untuk menggantikan ruang mungkin dalam bidang, diharapkan menjadi "unik" (atau sangat unlinkely). Jika Anda yakin tidak akan ada ruang di bidang ini, taruh saja bagian ini).sumber
tr
contoh untuk menunjukkan masalah ... Saya ingin menghindari panggilan sistem, jadi saya akan melihat opsi bash di luar${var##:}
yang saya sebutkan dalam komentar saya untuk glen ansewer .... Saya akan menunggu sebentar .. mungkin ada cara untuk membujuk IFS, kalau tidak, bagian pertama dari jawaban Anda adalah setelah ....IFS
itu sama di semua cangkang Bourne-style, itu ditentukan dalam POSIX .IFS
karakter sebagai pembatas-string. Pertanyaan saya paling baik dijawabjon_d
, tetapi jawaban @ nazad menunjukkan cara yang bagus untuk digunakanIFS
tanpa loop dan tanpa aplikasi utilitas.Karena bash IFS tidak menyediakan cara in-house untuk memperlakukan karakter pembatas berturut-turut sebagai pembatas tunggal (untuk pembatas non-spasi putih), saya telah menyusun versi semua bash (dengan menggunakan panggilan eksternal mis. Tr, awk, sed )
Itu dapat menangani multi-char IFS ..
Berikut ini adalah waktu pelaksanaannya, bersama dengan tes serupa untuk
tr
danawk
opsi yang ditampilkan pada halaman T / A ini ... Tes didasarkan pada 10.000 iterasi dari hanya membangun array (tanpa I / O) ...Ini outputnya
Ini skripnya
sumber
Anda juga bisa melakukannya dengan gawk, tetapi tidak cantik:
output
sumber
$var
ke${var##:}
... Saya benar-benar mencari cara untuk men-tweak IFS itu sendiri .. Saya ingin untuk melakukan ini tanpa panggilan eksternal (Saya punya perasaan bahwa bash dapat melakukan ini dengan lebih efisien daripada yang eksternal .. jadi saya akan tetap menggunakan jalur itu) ... metode Anda berfungsi (+1) .... Sejauh ini saat memodifikasi input, saya lebih suka mencobanya dengan bash, daripada awk atau tr (itu akan menghindari system call), tapi saya benar-benar nongkrong untuk tweak IFS ...bash 1.276s
...call (awk) 0m32.210s
,,,call (tr) 0m32.178s
... Lakukan itu beberapa kali dan Anda mungkin berpikir bash lambat! ... Apakah awk lebih mudah dalam hal ini? ... tidak jika Anda sudah memiliki snippet :) ... Saya akan mempostingnya nanti; harus pergi sekarang.var="The \"X\" factor:::A single '\"' crashes:::\"One Two\""
Jawaban sederhananya adalah: tutup semua pembatas menjadi satu (yang pertama).
Itu membutuhkan loop (yang berjalan kurang dari
log(N)
kali):Yang harus dilakukan adalah memisahkan string dengan benar pada satu pembatas, dan mencetaknya:
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):Dan cetak:
Masih lambat, tapi tidak lebih.
Perintah
command
tidak valid di Bourne.Di zsh,
command
panggilan hanya perintah eksternal dan membuat eval gagal jikacommand
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 perintahcommand
membuat kode dijalankan pada lebih banyak shell. Tetapi: menghapuscommand
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 tanpacommand
.Beberapa karakter IFS
Ya, IFS bisa multi karakter, tetapi setiap karakter akan menghasilkan satu argumen:
Akan menghasilkan:
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 perintahcommand
membuat zsh mencoba mencarieval
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
: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.
sumber
command eval
IIRC oleh Gilles