Проста відповідь: згорнути всі роздільники до одного (першого).
Для цього потрібен цикл (який працює менше log(N)
разів):
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
Залишилося правильно розділити рядок на один роздільник і роздрукувати його:
readarray -td "$f" arr < <(printf '%s%s' "$v"'' "$f")
printf '<%s>' "${arr[@]}" ; echo
Не потрібно set -f
ні змінювати IFS.
Тестується з пробілами, новими рядками та символами глобуса. Вся робота. Досить повільний (як слід очікувати, що це петля оболонки).
Але тільки для bash (bash 4.4+ через можливість -d
читання масиву).
ш
Версія оболонки не може використовувати масив, єдиний доступний масив - це позиційні параметри.
Використання tr -s
- це лише один рядок (IFS не змінюється в сценарії):
set -f; IFS=$f command eval set -- '$(echo "$var" | tr -s "$d" "[$f*]" )""'
І роздрукуйте:
printf '<%s>' "$@" ; echo
Ще повільно, але не набагато більше.
Команда command
в Борні недійсна.
У zsh command
викликає лише зовнішні команди та робить eval fail, якщо command
використовується.
У кш, навіть з command
, значення IFS змінюється в глобальному масштабі.
І command
робить спліт-провал у mksh, пов’язаних з оболонками (mksh, lksh, posh). Видалення команди command
змушує код працювати на більшіх оболонках. Але: вилучення command
змусить IFS зберегти своє значення у більшості оболонок (eval - це спеціальний вбудований), за винятком bash (без режиму posix) та zsh у режимі за замовчуванням (без емуляції) Цю концепцію не можна змусити працювати за замовчуванням zsh з або без command
.
IFS з декількома символами
Так, IFS може бути багатозначним, але кожен символ генерує один аргумент:
set -f; IFS="$d" command eval set -- '$(echo "$var" )""'
printf '<%s>' "$@" ; echo
Виведе:
<><a bc><><d ef><><><><><><><><>< ><><><><><><><><><
><><><><><><ghi><><><><><>
За допомогою bash ви можете опустити command
слово, якщо не в емуляції sh / POSIX. Команда не вдасться в ksh93 (IFS зберігає змінене значення). У команді zsh команда command
змушує zsh спробувати знайти eval
як зовнішню команду (яку вона не знаходить) і не вдасться.
Що відбувається, це те, що єдині символи IFS, які автоматично згортаються на один роздільник, - це пробіл IFS.
Один простір в IFS згортає всі послідовні пробіли до одного. Одна вкладка згортає всі вкладки. Один пробіл і одна вкладка згортають пробіли та / або вкладки до одного роздільника. Повторіть ідею з новим рядком.
Для обвалення декількох розмежувачів потрібні деякі жонглювання навколо.
Припускаючи, що ASCII 3 (0x03) не використовується у вході 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.
Більшість коментарів щодо ksh, zsh та bash (про command
та IFS) все ще стосуються тут.
Значення $'\0'
було б менш вірогідним при введенні тексту, але змінні bash не можуть містити NUL ( 0x00
).
У SH немає внутрішніх команд для виконання тих же строкових операцій, тому tr - єдине рішення для скриптів sh.