¿Cómo capturo stdin a una variable sin quitar ninguna línea nueva?

9

En un script de shell ...

¿Cómo capturo stdin a una variable sin quitar ninguna línea nueva?

En este momento he intentado:

var=`cat`
var=`tee`
var=$(tee)

En todos los casos $varno tendrá la nueva línea final de la secuencia de entrada. Gracias.

TAMBIÉN: Si no hay una nueva línea final en la entrada, entonces la solución no debe agregar una .

ACTUALIZACIÓN A LA LUZ DE LA RESPUESTA ACEPTADA:

La solución final que utilicé en mi código es la siguiente:

function filter() {
    #do lots of sed operations
    #see https://github.com/gistya/expandr for full code
}

GIT_INPUT=`cat; echo x`
FILTERED_OUTPUT=$(printf '%s' "$GIT_INPUT" | filter)
FILTERED_OUTPUT=${FILTERED_OUTPUT%x}
printf '%s' "$FILTERED_OUTPUT"

Si desea ver el código completo, consulte la página de github para expandr , un pequeño script de shell de filtro de expansión de palabras clave git de código abierto que desarrollé para fines de seguridad de la información. De acuerdo con las reglas establecidas en archivos .gitattributes (que pueden ser específicos de la rama) y git config , git canaliza cada archivo a través del script de shell expandr.sh cada vez que lo ingresa o sale del repositorio. (Es por eso que fue fundamental preservar las nuevas líneas finales, o la falta de ellas). Esto le permite limpiar información confidencial e intercambiar diferentes conjuntos de valores específicos del entorno para pruebas, puesta en escena y ramas vivas.

Tostada Coma
fuente
lo que haces aquí no es necesario. filtertoma stdin, corre sed. Se captura stdinen la $GIT_INPUTcontinuación de impresión que de nuevo a stdouttravés de una tubería de filtery coger su stdouten $FILTERED_OUTPUTy luego imprimirlo de nuevo a stdout. Todas las 4 líneas en la parte inferior de su ejemplo anterior podría ser reemplazado con sólo esto: filter. Sin ofender aquí, es solo que ... estás trabajando muy duro. No necesita las variables de shell la mayor parte del tiempo, solo dirija la entrada al lugar correcto y páselo.
mikeserv
No, lo que hago aquí es necesario porque si solo lo hago filter, agregará caracteres de nueva línea al final de cualquier flujo de entrada que inicialmente no termine en nuevas líneas. De hecho, originalmente lo hice, filterpero me encontré con ese problema que me llevó a esta solución porque ni "agregar nuevas líneas" ni "siempre quitar nuevas líneas" son soluciones aceptables.
CommaToast
sedprobablemente hará la nueva línea adicional, pero debe manejar eso filterno con todo el resto. Y todas esas funciones que tienes básicamente hacen lo mismo: a sed s///. Está utilizando el shell para canalizar los datos que ha guardado en su memoria, de sedmodo que sedpodría reemplazar esos datos con otros datos que el shell haya almacenado en su memoria para sedpoder volver a canalizarlos al shell. ¿Por qué no solo [ "$var" = "$condition" ] && var=new_value? Tampoco obtengo las matrices: ¿está almacenando el nombre de la matriz y [0]luego lo sedreemplaza con el valor [1]? Tal vez chatear?
mikeserv
@mikeserv - ¿Cuál sería el beneficio de mover ese código adentro filter? Funciona perfectamente como está. Con respecto a cómo funciona el código en mi enlace y por qué lo configuré de la manera en que lo hice, sí, hablemos de ello en una sala de chat.
CommaToast

Respuestas:

7

Las nuevas líneas finales se eliminan antes de que el valor se almacene en la variable. Es posible que desee hacer algo como:

var=`cat; echo x`

y usar en ${var%x}lugar de $var. Por ejemplo:

printf "%s" "${var%x}"

Tenga en cuenta que esto resuelve el problema de las nuevas líneas finales, pero no el byte nulo (si la entrada estándar no es texto), ya que de acuerdo con la sustitución del comando POSIX :

Si la salida contiene bytes nulos, el comportamiento no se especifica.

Pero las implementaciones de shell pueden preservar bytes nulos.

vinc17
fuente
¿Los archivos de texto suelen contener bytes nulos? No puedo ver por qué lo harían. Pero el guión que acabas de mencionar no parece funcionar.
CommaToast
Los archivos de texto @CommaToast no contienen bytes nulos. Pero la pregunta solo dice stdin / input stream, que puede no ser texto en el caso más general.
vinc17
OKAY. Bueno, lo intenté desde la línea de comandos y no hizo nada, y desde mi propio script, su sugerencia falla porque agrega "..." al final del archivo. Además, si no había una nueva línea allí, entonces todavía agrega una.
CommaToast
@CommaToast El "..." fue solo un ejemplo. He aclarado mi respuesta. No se agrega nueva línea (vea el texto antes del "..." en el ejemplo).
vinc17
1
Bueno, las conchas no deberían esconder cosas, eso no es genial. Esos proyectiles deberían ser disparados. No me gusta cuando mi computadora piensa que sabe mejor que yo.
CommaToast
4

Puede usar el readincorporado para lograr esto:

$ IFS='' read -d '' -r foo < <(echo bar)

$ echo "<$foo>"
<bar
>

Para que un script lea STDIN, simplemente sería:

IFS='' read -d '' -r foo

 

Sin embargo, no estoy seguro de en qué proyectiles funcionará esto. Pero funciona bien tanto en bash como en zsh.

Patricio
fuente
Ni -dla sustitución del proceso ( <(...)) son portátiles; este código no funcionará dash, por ejemplo.
chepner
Bueno, la sustitución del proceso no es parte de la respuesta, eso fue solo parte del ejemplo que muestra que funciona. En cuanto a -d, por eso puse el descargo de responsabilidad en la parte inferior. El OP no especifica el shell.
Patrick
@chepner: si bien el estilo difiere ligeramente, el concepto ciertamente funciona dash. Simplemente use <<HEREDOC\n$(gen input)\nHEREDOC\n- in dash- que usa tuberías para heredocs de la misma manera que otros shells los usan para la sustitución de procesos: no hay diferencia. La read -dcuestión es solo especificar un delimitador: puede hacer lo mismo una docena de formas, solo asegúrese de hacerlo. Aunque necesitarás algo de cola gen input.
mikeserv
Establece IFS = '' para que no ponga espacios entre las líneas que lee en eh? Buen truco.
CommaToast
En realidad, en este caso, IFS=''probablemente no sea necesario. Está destinado para que readno colapsen espacios. Pero cuando se lee en una sola variable, no tiene ningún efecto (que pueda recordar). Pero me siento más seguro dejándolo encendido :-)
Patrick
2

Puedes hacer como:

input | { var=$(sed '$s/$/./'); var=${var%.}; }

Cualquier cosa que hagas $vardesaparece tan pronto como salgas de esa { current shell ; }agrupación de todos modos. Pero también podría funcionar así:

var=$(input | sed '$s/$/./'); var=${var%.}
mikeserv
fuente
1
Cabe señalar que con la primera solución, es decir, tener que usar $varen la { ... }agrupación, no siempre es posible. Por ejemplo, si este comando se ejecuta dentro de un bucle y se necesita $varfuera del bucle.
vinc17
@ vinc17: si es un bucle que deseaba usar, lo usaría en lugar de las {}llaves. Es cierto, y se señala explícitamente en la respuesta , que $vares muy probable que el valor de desaparezca por completo cuando la { current shell; }agrupación es cerrado. ¿Hay alguna forma más explícita de decirlo que, lo que sea que hagas $vardesaparece ...?
mikeserv
@ vinc17 - probablemente la mejor manera, sin embargo:input | sed "s/'"'/&"&"&/g;s/.*/process2 '"'-> &'/" | sh
mikeserv
1
También existe la _variablesfunción de bash_completion, que almacena el resultado de una sustitución de comando en una variable global COMPREPLY. Si se usara una solución de tubería para mantener nuevas líneas, el resultado se perdería. En su respuesta, uno tiene la impresión de que ambas soluciones son igualmente buenas. Además, debe tenerse en cuenta que el comportamiento de la solución de canalización depende en gran medida del shell: un usuario podría probar echo foo | { var=$(sed '$s/$/./'); var=${var%.}; } ; echo $varcon ksh93 y zsh, y piensa que está bien, mientras que este código tiene errores.
vinc17
1
No dijiste "no funciona". Acaba de decir " $vardesaparece" (lo que en realidad no es cierto, ya que esto depende del shell; POSIX no especifica el comportamiento), que es una oración bastante neutral. La segunda solución es mejor porque no sufre este problema y su comportamiento es consistente en todos los shells POSIX.
vinc17