Para la nueva pregunta, este script funciona:
#!/bin/bash
f() { for i in $(seq "$((RANDOM % 3 ))"); do
echo;
done; return $((RANDOM % 256));
}
exact_output(){ out=$( $1; ret=$?; echo x; exit "$ret" );
unset OldLC_ALL ; [ "${LC_ALL+set}" ] && OldLC_ALL=$LC_ALL
LC_ALL=C ; out=${out%x};
unset LC_ALL ; [ "${OldLC_ALL+set}" ] && LC_ALL=$OldLC_ALL
printf 'Output:%10q\nExit :%2s\n' "${out}" "$?"
}
exact_output f
echo Done
En ejecución:
Output:$'\n\n\n'
Exit :25
Done
La descripción más larga
La sabiduría habitual para los shells POSIX para lidiar con la eliminación de \n
es:
agregar un x
s=$(printf "%s" "${1}x"); s=${s%?}
Eso es necesario porque la última línea ( S ) nueva se elimina mediante la expansión del comando según la especificación POSIX :
eliminar secuencias de uno o más caracteres al final de la sustitución.
Sobre un rastro x
.
Se ha dicho en esta pregunta que x
podría confundirse con el byte final de algún carácter en alguna codificación. Pero, ¿cómo vamos a adivinar qué o qué personaje es mejor en algún idioma en alguna codificación posible? Esa es una propuesta difícil, por decir lo menos.
Sin embargo; Eso es simplemente incorrecto .
La única regla que debemos seguir es agregar exactamente lo que eliminamos.
Debería ser fácil comprender que si agregamos algo a una cadena existente (o secuencia de bytes) y luego eliminamos exactamente lo mismo, la cadena original (o secuencia de bytes) debe ser la misma.
¿Dónde nos equivocamos? Cuando mezclamos caracteres y bytes .
Si agregamos un byte, debemos eliminar un byte, si agregamos un carácter debemos eliminar exactamente el mismo carácter .
La segunda opción, agregar un carácter (y luego eliminar exactamente el mismo carácter) puede volverse complicado y complejo, y, sí, las páginas de códigos y las codificaciones pueden interferir.
Sin embargo, la primera opción es bastante posible y, después de explicarla, se volverá simple.
Agreguemos un byte, un byte ASCII (<127), y para mantener las cosas lo menos complicadas posible, digamos un carácter ASCII en el rango de az. O como deberíamos decirlo, un byte en el rango hexadecimal 0x61
- 0x7a
. Elija cualquiera de esos, tal vez una x (realmente un byte de valor 0x78
). Podemos agregar dicho byte concatenando una x a una cadena (supongamos que un é
):
$ a=é
$ b=${a}x
Si miramos la cadena como una secuencia de bytes, vemos:
$ printf '%s' "$b" | od -vAn -tx1c
c3 a9 78
303 251 x
Una secuencia de cuerdas que termina en una x.
Si eliminamos esa x (valor de byte 0x78
), obtenemos:
$ printf '%s' "${b%x}" | od -vAn -tx1c
c3 a9
303 251
Funciona sin problemas.
Un ejemplo un poco más difícil.
Digamos que la cadena que nos interesa termina en byte 0xc3
:
$ a=$'\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x20\xc3'
Y agreguemos un byte de valor 0xa9
$ b=$a$'\xa9'
La cadena se ha convertido en esto ahora:
$ echo "$b"
a test string é
Exactamente lo que quería, los últimos dos bytes son un carácter en utf8 (para que cualquiera pueda reproducir estos resultados en su consola utf8).
Si eliminamos un carácter, se cambiará la cadena original. Pero eso no es lo que agregamos, agregamos un valor de byte, que se escribe como una x, pero un byte de todos modos.
Lo que necesitamos para evitar malinterpretar bytes como caracteres. Lo que necesitamos es una acción que elimine el byte que usamos 0xa9
. De hecho, ash, bash, lksh y mksh parecen hacer exactamente eso:
$ c=$'\xa9'
$ echo ${b%$c} | od -vAn -tx1c
61 20 74 65 73 74 20 73 74 72 69 6e 67 20 c3 0a
a t e s t s t r i n g 303 \n
Pero no ksh o zsh.
Sin embargo, eso es muy fácil de resolver, digamos a todos esos shells que eliminen los bytes:
$ LC_ALL=C; echo ${b%$c} | od -vAn -tx1c
eso es todo, todos los shells probaron el trabajo (excepto yash) (para la última parte de la cadena):
ash : s t r i n g 303 \n
dash : s t r i n g 303 \n
zsh/sh : s t r i n g 303 \n
b203sh : s t r i n g 303 \n
b204sh : s t r i n g 303 \n
b205sh : s t r i n g 303 \n
b30sh : s t r i n g 303 \n
b32sh : s t r i n g 303 \n
b41sh : s t r i n g 303 \n
b42sh : s t r i n g 303 \n
b43sh : s t r i n g 303 \n
b44sh : s t r i n g 303 \n
lksh : s t r i n g 303 \n
mksh : s t r i n g 303 \n
ksh93 : s t r i n g 303 \n
attsh : s t r i n g 303 \n
zsh/ksh : s t r i n g 303 \n
zsh : s t r i n g 303 \n
Así de simple, dígale al shell que elimine un carácter LC_ALL = C, que es exactamente un byte para todos los valores de byte de 0x00
a 0xff
.
Solución para comentarios:
Para el ejemplo discutido en los comentarios, una posible solución (que falla en zsh) es:
#!/bin/bash
LC_ALL=zh_HK.big5hkscs
a=$(printf '\210\170');
b=$(printf '\170');
unset OldLC_ALL ; [ "${LC_ALL+set}" ] && OldLC_ALL=$LC_ALL
LC_ALL=C ; a=${a%"$b"};
unset LC_ALL ; [ "${OldLC_ALL+set}" ] && LC_ALL=$OldLC_ALL
printf '%s' "$a" | od -vAn -c
Eso eliminará el problema de la codificación.
$IFS
, por lo que no se capturará como argumento.