Простой ответ: :свернуть все разделители в один (первый ).
Для этого требуется цикл (, который выполняется менее 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
недействительна в Борне.
в зш,command
вызывает только внешние команды и приводит к сбою eval, если используется command
.
В ksh даже с command
значение IFS изменяется в глобальной области видимости.
И command
приводит к сбою разделения в оболочках, связанных с mksh (mksh, lksh, posh )Удаление команды command
приводит к запуску кода на большем количестве оболочек. Но :удаление command
заставит IFS сохранить свое значение в большинстве оболочек (eval — это специальная встроенная функция ), за исключением bash (без режима posix )и zsh по умолчанию (без режима эмуляции ). Эта концепция не может работать в zsh по умолчанию ни с command
, ни без нее.
Да, 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.
Тот факт, что r -L
не работал должным образом, был признан ошибкой, когда я спросил об этом в списке рассылки zsh-users
.
Ошибка была исправлена 18 февраля 2021 г. в ветке разработки оболочки Git:
commit 6bef719302d6db33c63fb6f2636986dff1941ac2
Author: Peter Stephenson <retracted>
Date: Thu Feb 18 21:37:08 2021 +0000
users/26509: fix for r -L
fc with the -L option should ignore remote entires, rather than
reading them and treating them as an error.
Вам нужно будет использовать виджет set-local-history
для этого:
bindkey '^[r' set-local-history
При этом вы можете нажать AltR для переключения между использованием локальной или глобальной истории для таких команд, как r
и fc
. Если вы не помните, находитесь ли вы в режиме локальной или глобальной истории, нажмите Alt0 + AltR для включения режима глобальной истории или Alt1 + AltR для принудительного режима локальной истории (при условии, что вы используете раскладку клавиатуры по умолчанию emacs
).
Почему rc -L
или fc -e - -L
не подходят для вашего случая, я не знаю. Это звучит как ошибка для меня. Вы можете попробовать спросить об этом в списке рассылки Zsh , чтобы узнать, считают ли разработчики это ошибкой или это сделано намеренно.