La respuesta simple es: colapsar todos los delimitadores en uno (el primero).
Eso requiere un bucle (que se ejecuta menos de log(N)
veces):
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
Todo lo que queda por hacer es dividir correctamente la cadena en un delimitador e imprimirla:
readarray -td "$f" arr < <(printf '%s%s' "$v"'' "$f")
printf '<%s>' "${arr[@]}" ; echo
No es necesario set -f
ni cambiar IFS.
Probado con espacios, líneas nuevas y caracteres globales. Todo el trabajo. Bastante lento (como debería esperarse que sea un ciclo de shell).
Pero solo para bash (bash 4.4+ debido a la opción -d
de readarray).
sh
Una versión de shell no puede usar una matriz, la única matriz disponible son los parámetros posicionales.
Usar tr -s
es solo una línea (IFS no cambia en el script):
set -f; IFS=$f command eval set -- '$(echo "$var" | tr -s "$d" "[$f*]" )""'
E imprimirlo:
printf '<%s>' "$@" ; echo
Todavía lento, pero no mucho más.
El comando command
no es válido en Bourne.
En zsh, command
llama solo a comandos externos y hace que eval falle si command
se usa.
En ksh, incluso con command
, el valor de IFS cambia en el ámbito global.
Y command
hace que la división falle en los shells relacionados con mksh (mksh, lksh, posh) Al eliminar el comando, command
el código se ejecuta en más shells. Pero: la eliminación command
hará que IFS conserve su valor en la mayoría de los shells (eval es un valor incorporado especial) excepto en bash (sin modo posix) y zsh en modo predeterminado (sin emulación). No se puede hacer que este concepto funcione en zsh predeterminado con o sin command
.
Múltiples caracteres IFS
Sí, IFS podría tener varios caracteres, pero cada carácter generará un argumento:
set -f; IFS="$d" command eval set -- '$(echo "$var" )""'
printf '<%s>' "$@" ; echo
Saldrá:
<><a bc><><d ef><><><><><><><><>< ><><><><><><><><><
><><><><><><ghi><><><><><>
Con bash, puede omitir la command
palabra si no está en la emulación sh / POSIX. El comando fallará en ksh93 (IFS mantiene el valor cambiado). En zsh, el comando command
hace que zsh intente buscar eval
como un comando externo (que no encuentra) y falla.
Lo que sucede es que los únicos caracteres IFS que se contraen automáticamente en un delimitador son espacios en blanco IFS.
Un espacio en IFS colapsará todos los espacios consecutivos en uno. Una pestaña colapsará todas las pestañas. Un espacio y una pestaña contraerán series de espacios y / o pestañas en un delimitador. Repita la idea con nueva línea.
Para colapsar varios delimitadores se requieren algunos malabarismos.
Suponiendo que ASCII 3 (0x03) no se usa en la entrada 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.
La mayoría de los comentarios sobre ksh, zsh y bash (about command
e IFS) todavía se aplican aquí.
Un valor de $'\0'
sería menos probable en la entrada de texto, pero las variables bash no pueden contener NUL ( 0x00
).
No hay comandos internos en sh para hacer las mismas operaciones de cadena, por lo que tr es la única solución para los scripts sh.