Quiero poder capturar el resultado exacto de una sustitución de comando, incluidos los nuevos caracteres de línea finales .
Me doy cuenta de que están despojados de forma predeterminada, por lo que es posible que se requiera alguna manipulación para mantenerlos, y quiero mantener el código de salida original .
Por ejemplo, dado un comando con un número variable de nuevas líneas finales y código de salida:
f(){ for i in $(seq "$((RANDOM % 3))"); do echo; done; return $((RANDOM % 256));}
export -f f
Quiero ejecutar algo como:
exact_output f
Y que la salida sea:
Output: $'\n\n'
Exit: 5
Estoy interesado en ambos bash
y POSIX sh
.
$IFS
, por lo que no se capturará como argumento.IFS
(try( IFS=:; subst=$(printf 'x\n\n\n'); printf '%s' "$subst" )
Solamente los saltos de línea quedan despojados..\t
Y `` no, yIFS
no lo afecta.tcsh
Respuestas:
POSIX conchas
El truco habitual ( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ) para obtener el stdout completo de un comando es hacer:
La idea es agregar y extra
.\n
. La sustitución de comandos solo eliminará eso\n
. Y te quitas el.
con${output%.}
.Tenga en cuenta que en shells que no sean
zsh
, eso todavía no funcionará si la salida tiene bytes NUL. Conyash
, eso no funcionará si la salida no es texto.También tenga en cuenta que en algunas configuraciones regionales, importa qué carácter use para insertar al final.
.
en general debería estar bien, pero otros podrían no estarlo. Por ejemplox
(como se usa en algunas otras respuestas) o@
no funcionaría en un entorno local utilizando los conjuntos de caracteres BIG5, GB18030 o BIG5HKSCS. En esos charsets, la codificación de varios caracteres termina en el mismo byte que la codificación dex
o@
(0x78, 0x40)Por ejemplo,
ū
en BIG5HKSCS es 0x88 0x78 (yx
es 0x78 como en ASCII, todos los conjuntos de caracteres en un sistema deben tener la misma codificación para todos los caracteres del conjunto de caracteres portátil que incluye letras en inglés,@
y.
). Entonces, sicmd
fue asíprintf '\x88'
y lo insertamosx
después,${output%x}
no podríamos quitar esox
como$output
lo que realmente contieneū
.En
.
cambio, el uso podría conducir al mismo problema en teoría si hubiera caracteres cuya codificación termine en la misma codificación que.
, pero por haberlo comprobado hace algún tiempo, puedo decir que ninguno de los conjuntos de caracteres que pueden estar disponibles para su uso en un entorno local un sistema Debian, FreeBSD o Solaris tiene esos caracteres, lo cual es lo suficientemente bueno para mí (y por qué me he decidido,.
que también es el símbolo para marcar el final de una oración en inglés, por lo que parece apropiado).Un enfoque más correcto según lo discutido por @Arrow sería cambiar la configuración regional a C solo para eliminar el último carácter (
${output%.}
), lo que aseguraría que solo se elimine un byte, pero eso complicaría significativamente el código y potencialmente introduciría problemas de compatibilidad de su propio.alternativas bash / zsh
Con
bash
yzsh
, suponiendo que la salida no tenga NUL, también puede hacer:Para obtener el estado de salida
cmd
, se puede hacerwait "$!"; ret=$?
enbash
pero no enzsh
.rc / es / akanaga
Para completar, tenga en cuenta que
rc
/es
/akanga
tener un operador para eso. En ellos, la sustitución de comandos, expresada como`cmd
(o`{cmd}
para comandos más complejos) devuelve una lista (al dividir en$ifs
, espacio-tab-nueva línea por defecto). En esos shells (a diferencia de los shells tipo Bourne), la eliminación de la nueva línea solo se realiza como parte de esa$ifs
división. Por lo tanto, puede vaciar$ifs
o usar el``(seps){cmd}
formulario donde especifica los separadores:o:
En cualquier caso, se pierde el estado de salida del comando. Tendría que incrustarlo en la salida y extraerlo después, lo que sería feo.
pescado
En peces, la sustitución de comandos es con
(cmd)
y no implica una subshell.Crea una
$var
matriz con todas las líneas en la salida decmd
if$IFS
no está vacía, o con la salida de un carácter de nueva líneacmd
despojado de hasta uno (a diferencia de todos en la mayoría de los demás shells) si$IFS
está vacío.Entonces todavía hay un problema en eso
(printf 'a\nb')
y(printf 'a\nb\n')
expandirse a lo mismo incluso con un vacío$IFS
.Para evitar eso, lo mejor que se me ocurrió fue:
Una alternativa es hacer:
Concha Bourne
El shell Bourne no admitía la
$(...)
forma ni el${var%pattern}
operador, por lo que puede ser bastante difícil de lograr allí. Un enfoque es utilizar eval y citar:Aquí estamos generando un
para ser pasado a
eval
. En cuanto al enfoque POSIX, si'
fuera uno de esos caracteres cuya codificación se puede encontrar al final de otros caracteres, tendríamos un problema (mucho peor ya que se convertiría en una vulnerabilidad de inyección de comando), pero afortunadamente, como.
, no es uno de esos, y esa técnica de cita es generalmente la que usa cualquier cosa que cita código de shell (tenga en cuenta que\
tiene el problema, por lo que no debe usarse (también excluye"..."
dentro de lo que necesita usar barras diagonales inversas para algunos caracteres) Aquí, solo lo estamos usando después de lo'
cual está bien).tcsh
Ver tcsh preservar nuevas líneas en la sustitución de comandos `...`
(sin tener en cuenta el estado de salida, que puede abordar guardándolo en un archivo temporal (
echo $status > $tempfile:q
después del comando))fuente
zsh
puede almacenarNUL
en una variable, ¿por qué noIFS= read -rd '' output < <(cmd)
funcionaría? Necesita poder almacenar la longitud de una cadena ... ¿codifica''
como una cadena de 1 byte en\0
lugar de una cadena de 0 bytes?read -d ''
se trata comoread -d $'\0'
(bash
aunque también$'\0'
es igual que en''
todas partes).x
si eso es lo que se agregó. Por favor, eche un vistazo a mi respuesta editada.var=value command eval
truco se discutió aquí ( también ) y en la lista de correo del grupo Austin antes. Descubrirá que no es portátil (y es bastante obvio cuando intenta cosas comoa=1 command eval 'unset a; a=2'
o peor que no está destinado a usarse así). Lo mismo para elsavedVAR=$VAR;...;VAR=$savedVAR
que no hace lo que quieres cuando$VAR
inicialmente no está configurado. Si eso es solo para solucionar un problema teórico (un error que no se puede solucionar en la práctica), en mi opinión, no vale la pena. Aún así, te apoyaré por intentarlo.LANG=C
para eliminar un byte de una cadena? Estás planteando inquietudes en torno al punto real, todo es fácil de resolver. (1) no hay unset sin usar (2) Pruebe la variable antes de cambiarla. @ StéphaneChazelasPara la nueva pregunta, este script funciona:
En ejecución:
La descripción más larga
La sabiduría habitual para los shells POSIX para lidiar con la eliminación de
\n
es:Eso es necesario porque la última línea ( S ) nueva se elimina mediante la expansión del comando según la especificación POSIX :
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 valor0x78
). Podemos agregar dicho byte concatenando una x a una cadena (supongamos que uné
):Si miramos la cadena como una secuencia de bytes, vemos:
Una secuencia de cuerdas que termina en una x.
Si eliminamos esa x (valor de byte
0x78
), obtenemos:Funciona sin problemas.
Un ejemplo un poco más difícil.
Digamos que la cadena que nos interesa termina en byte
0xc3
:Y agreguemos un byte de valor
0xa9
La cadena se ha convertido en esto ahora:
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:Pero no ksh o zsh.
Sin embargo, eso es muy fácil de resolver, digamos a todos esos shells que eliminen los bytes:
eso es todo, todos los shells probaron el trabajo (excepto yash) (para la última parte de la cadena):
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
a0xff
.Solución para comentarios:
Para el ejemplo discutido en los comentarios, una posible solución (que falla en zsh) es:
Eso eliminará el problema de la codificación.
fuente
zsh
agregadoprintf -v
para compatibilidad conbash
diciembre de 2015${var%?}
siempre elimine un byte es más correcto en teoría, pero: 1-LC_ALL
yLC_CTYPE
anular$LANG
, por lo que necesitaría establecerLC_ALL=C
2- no puede hacervar=${var%?}
en una subcapa como el cambio lo haría perderse, por lo que necesitaría guardar y restaurar el valor y el estadoLC_ALL
(o recurrir alocal
funciones de alcance que no sean POSIX ) 3- cambiar la configuración regional a mitad del guión no es totalmente compatible con algunos shells como yash. Por otro lado, en la práctica.
nunca es un problema en los charsets de la vida real, por lo que usarlo evita mezclarse con LC_ALL.Puede generar un carácter después de la salida normal y luego despojarlo:
Esta es una solución compatible con POSIX.
fuente