Considera este script:
tmpfile=$(mktemp)
cat <<EOS > "$tmpfile"
line 1
line 2
line 3
EOS
cat <(tail -1 "$tmpfile") "$tmpfile"
Esto funciona y produce:
line 3
line 1
line 2
line 3
Digamos que nuestra fuente de entrada, en lugar de ser un archivo real, era stdin:
cat <<EOS | # what goes here now?
line 1
line 2
line 3
EOS
¿Cómo modificamos el comando?
cat <(tail -1 "$tmpfile") "$tmpfile"
¿De modo que todavía produce la misma salida, en este contexto diferente?
NOTA: El Heredoc específico que estoy buscando, así como el uso de un Heredoc en sí mismo, es meramente ilustrativo. Cualquier respuesta aceptable debe suponer que está recibiendo datos arbitrarios a través de stdin .
Respuestas:
Tratar:
Ejemplo
Defina una variable con nuestra entrada:
Ejecute nuestro comando:
Alternativamente, por supuesto, podríamos usar un documento aquí:
Cómo funciona
x=x $0 ORS
Esto agrega cada línea de entrada a la variable
x
.En awk,
ORS
es el separador de registro de salida . Por defecto, es un carácter de nueva línea.END{printf "%s", $0 ORS x}
Después de haber leído todo el archivo, imprime la última línea
$0
, seguida del contenido de todo el archivox
.Como esto lee toda la entrada en la memoria, no sería apropiado para entradas grandes ( por ejemplo, gigabytes).
fuente
tee
, pero de un stdin y un archivo, estaríamos canalizando el mismo stdin en dos sustituciones de proceso diferentes. o algo que sería más o menos equivalente a eso?Si stdin apunta a un archivo que se puede buscar (como en el caso de los documentos aquí de bash (pero no todos los demás shell) que se implementan con archivos temporales), puede obtener la cola y luego buscar antes de leer el contenido completo:
Los operadores de búsqueda están disponibles en los shells
zsh
oksh93
, o los lenguajes de script como tcl / perl / python, pero no enbash
. Pero siempre puede llamar a esos intérpretes más avanzadosbash
si tiene que usarbash
.O
Ahora, eso no funcionará cuando stdin apunte a archivos no buscables como una tubería o un socket. Entonces, la única opción es leer y almacenar (en la memoria o en un archivo temporal ...) toda la entrada.
Ya se han dado algunas soluciones para almacenar en la memoria.
Con un archivo temporal, con
zsh
, puedes hacerlo con:Si en Linux, con
bash
o conzsh
cualquier shell que use archivos temporales para documentos aquí, en realidad podría usar el archivo temporal creado por un documento aquí para almacenar la salida:fuente
El problema con traducir esto a algo que usa
tail
es quetail
necesita leer todo el archivo para encontrar el final. Para usar eso en su tubería, necesitatail
.cat
.La parte difícil no es duplicar el contenido del documento (lo
tee
hace) sino lograr que la salidatail
suceda antes de que salga el resto del documento, sin usar un archivo temporal intermedio.El uso
sed
(oawk
, como lo hace John1024 ) elimina el doble análisis de los datos y el problema de ordenar almacenando los datos en la memoria.La
sed
solución que propongo es1{h;d;}
, almacene la primera línea en el espacio de espera, tal cual, y salte a la siguiente línea.H
, agregue entre sí la línea al espacio de espera con una nueva línea incrustada.${G;p;}
, agregue el espacio de espera a la última línea con una nueva línea incrustada e imprima los datos resultantes.Esta es una traducción bastante literal de la solución de John1024
sed
, con la advertencia de que el estándar POSIX solo garantiza que el espacio de retención sea de al menos 8192 bytes (8 KiB; pero recomienda que este búfer se asigne dinámicamente y se expanda según sea necesario, lo que tanto GNUsed
y BSD losed
está haciendo).Si te permites usar una tubería con nombre:
Esto se utiliza
tee
para enviar los datos hacia abajomypipe
y al mismo tiempo acat
. Lacat
utilidad primero leerá la salida detail
(que leemypipe
, quetee
está escribiendo), y luego agregará la copia del documento que viene directamentetee
.Sin embargo, hay una falla grave en esto, ya que si el documento es demasiado grande (más grande que el tamaño del búfer de la tubería),
tee
la escrituramypipe
y elcat
bloqueo se bloquearán mientras se espera que la tubería (sin nombre) se vacíe. No se vaciaría hasta que secat
leyera.cat
no leería de él hasta quetail
hubiera terminado. Ytail
no terminaría hasta quetee
hubiera terminado. Esta es una situación clásica de punto muerto.La variación
tiene el mismo problema
fuente
sed
uno no funciona si la entrada tiene solo una línea (tal vezsed '1h;1!H;$!d;G'
). También tenga en cuenta que variassed
implementaciones tienen un límite bajo en el tamaño de su patrón y tienen espacio.Hay una herramienta nombrada
pee
en una colección de utilidades de línea de comandos generalmente empaquetadas con el nombre "moreutils" (o recuperables de su sitio web de origen ).Si puede tenerlo en su sistema, entonces el equivalente para su ejemplo sería:
El orden de los comandos ejecutados
pee
es importante porque se ejecutan en la secuencia proporcionada.fuente
Tratar:
Dado que todo es información literal (un "documento aquí es"), y la diferencia entre este y la salida deseada es trivial, simplemente masajee esa información literal allí para que coincida con la salida.
Ahora supongamos que
line 3
proviene de algún lugar y se almacena en una variable llamadalastline
:En un documento here, podemos generar texto sustituyendo variables. No solo eso, sino que podemos calcular el texto usando la sustitución de comandos:
Podemos interpolar múltiples líneas:
En general, evite el procesamiento de texto de la plantilla here doc; intenta generarlo usando código interpolado.
fuente
cat <<EOS...
en el OP fue solo un ejemplo de "capturar un archivo arbitrario", para hacer que la publicación sea específica y la pregunta sea clara. ¿No fue realmente obvio para usted, o simplemente pensó que sería inteligente interpretar la pregunta literalmente?Si no te importa el pedido. Entonces esto funcionará
cat lines | tee >(tail -1)
. Como han dicho otros. Debe leer el archivo dos veces, o guardar el archivo completo en el búfer, en el orden que solicitó.fuente