En shell, ¿cómo puedo tailcrear el último archivo en un directorio?
En shell, ¿cómo puedo tailcrear el último archivo en un directorio?
Respuestas:
¡ No analice la salida de ls! Analizar la salida de ls es difícil y poco confiable .
Si debe hacer esto, le recomiendo usar find. Originalmente tenía aquí un ejemplo simple simplemente para darle una idea general de la solución, pero dado que esta respuesta parece algo popular, decidí revisarla para proporcionar una versión segura para copiar / pegar y usar con todas las entradas. ¿Estás sentado cómodamente? Comenzaremos con un oneliner que le dará el último archivo en el directorio actual:
tail -- "$(find . -maxdepth 1 -type f -printf '%T@.%p\0' | sort -znr -t. -k1,2 | while IFS= read -r -d '' -r record ; do printf '%s' "$record" | cut -d. -f3- ; break ; done)"
No es un buen línea ahora, ¿verdad? Aquí está nuevamente como una función de shell y formateada para facilitar la lectura:
latest-file-in-directory () {
find "${@:-.}" -maxdepth 1 -type f -printf '%T@.%p\0' | \
sort -znr -t. -k1,2 | \
while IFS= read -r -d '' -r record ; do
printf '%s' "$record" | cut -d. -f3-
break
done
}
Y ahora eso como línea:
tail -- "$(latest-file-in-directory)"
Si todo lo demás falla, puede incluir la función anterior en su .bashrcy considerar el problema resuelto, con una advertencia. Si solo desea hacer el trabajo, no necesita leer más.
La advertencia con esto es que un nombre de archivo que termina en una o más líneas nuevas todavía no se pasará tailcorrectamente. Evitar este problema es complicado y considero que es suficiente que si se encuentra un nombre de archivo malicioso de este tipo, se produzca un comportamiento relativamente seguro de encontrar un error "No existe ese archivo" en lugar de algo más peligroso.
Para los curiosos, esta es la tediosa explicación de cómo funciona, por qué es seguro y por qué otros métodos probablemente no lo son.
En primer lugar, el único byte que es seguro para delimitar las rutas de archivos es nulo porque es el único byte universalmente prohibido en las rutas de archivos en sistemas Unix. Cuando se maneja cualquier lista de rutas de archivos, es importante utilizar solo nulo como delimitador y, al entregar incluso una sola ruta de archivo de un programa a otro, hacerlo de una manera que no ahogue bytes arbitrarios. Hay muchas formas aparentemente correctas de resolver este y otros problemas que fallan al suponer (incluso accidentalmente) que los nombres de los archivos no tendrán nuevas líneas o espacios en ellos. Ninguna de las suposiciones es segura.
Para los propósitos de hoy, el primer paso es obtener una lista de archivos delimitada por nulos fuera de búsqueda. Esto es bastante fácil si tiene un findsoporte -print0como GNU:
find . -print0
Pero esta lista todavía no nos dice cuál es la más nueva, por lo que debemos incluir esa información. Elijo usar el -printfinterruptor de find que me permite especificar qué datos aparecen en la salida. No todas las versiones de findsoporte -printf(no es estándar) pero GNU find sí. Si se encuentra sin -printfusted, necesitará confiar -exec stat {} \;en qué momento debe renunciar a toda esperanza de portabilidad, ya statque tampoco es estándar. Por ahora voy a seguir asumiendo que tienes herramientas GNU.
find . -printf '%T@.%p\0'
Aquí solicito el formato printf, %T@que es el tiempo de modificación en segundos desde el comienzo de la época de Unix seguido de un punto y luego un número que indica fracciones de segundo. Agrego a esto otro período y luego %p(que es la ruta completa al archivo) antes de terminar con un byte nulo.
Ahora tengo
find . -maxdepth 1 \! -type d -printf '%T@.%p\0'
Puede ser obvio, pero por el hecho de estar completo -maxdepth 1evita que se findenumeren los contenidos de los subdirectorios y \! -type domite los directorios que es poco probable que desee tail. Hasta ahora tengo archivos en el directorio actual con información de tiempo de modificación, así que ahora necesito ordenar por ese tiempo de modificación.
De forma predeterminada, sortespera que su entrada sean registros delimitados por nueva línea. Si tiene GNU sort, puede pedirle que espere registros delimitados por nulos utilizando el -zinterruptor .; para estándar sortno hay solución. Solo estoy interesado en ordenar por los dos primeros números (segundos y fracciones de segundo) y no quiero ordenar por el nombre real del archivo, así que digo sortdos cosas: Primero, que debería considerar el punto ( .) como un delimitador de campo y segundo, que solo debe usar los campos primero y segundo al considerar cómo ordenar los registros.
| sort -znr -t. -k1,2
En primer lugar, estoy agrupando tres opciones cortas que no tienen valor juntas; -znres solo una forma concisa de decir -z -n -r). Después de eso -t .(el espacio es opcional) le dice sortal carácter delimitador de campo y -k 1,2especifica los números de campo: primero y segundo ( sortcuenta los campos de uno, no cero). Recuerde que un registro de muestra para el directorio actual se vería así:
1000000000.0000000000../some-file-name
Esto significa que sortse verá primero 1000000000y luego 0000000000al ordenar este registro. La -nopción le indica sortque use la comparación numérica al comparar estos valores, porque ambos valores son números. Esto puede no ser importante ya que los números son de longitud fija pero no hace daño.
El otro interruptor dado sortes -rpara "reversa". Por defecto, la salida de una ordenación numérica será primero los números más bajos, los -rcambia para que enumere los números más bajos al final y los números más altos primero. Dado que estos números son marcas de tiempo más altas, significará más nuevos y esto coloca el registro más nuevo al comienzo de la lista.
A medida que emerge la lista de rutas de archivo sort, ahora tiene la respuesta deseada que estamos buscando en la parte superior. Lo que queda es encontrar una manera de descartar los otros registros y eliminar la marca de tiempo. Desafortunadamente, incluso GNU heady tailno aceptan interruptores para que funcionen con entradas delimitadas por nulos. En cambio, uso un bucle while como una especie de hombre pobre head.
| while IFS= read -r -d '' record
Primero lo desarmo IFSpara que la lista de archivos no esté sujeta a división de palabras. A continuación, digo readdos cosas: no interprete secuencias de escape en la entrada ( -r) y la entrada se delimita con un byte nulo ( -d); aquí la cadena vacía ''se usa para indicar "sin delimitador", también delimitado por nulo. Cada registro se leerá en la variable recordpara que cada vez que el whileciclo se repita, tenga una sola marca de tiempo y un solo nombre de archivo. Tenga en cuenta que -des una extensión GNU; Si solo tiene un estándar, readesta técnica no funcionará y tendrá pocos recursos.
Sabemos que la recordvariable tiene tres partes, todas delimitadas por caracteres de punto. Usando la cututilidad es posible extraer una parte de ellos.
printf '%s' "$record" | cut -d. -f3-
Aquí se pasa todo el registro hacia printfy desde allí canalizado a cut; en bash se podía simplificar este adicionalmente utilizando una cadena de aquí a cut -d. -3f- <<<"$record"un mejor rendimiento. Decimos cutdos cosas: primero con -deso, debe un delimitador específico para identificar campos (como con sortel delimitador .se utiliza). El segundo cutrecibe instrucciones -fpara imprimir solo valores de campos específicos; la lista de campos se proporciona como un rango 3-que indica el valor del tercer campo y de todos los campos siguientes. Esto significa que cutleerá e ignorará todo, incluido el segundo .que encuentre en el registro, y luego imprimirá el resto, que es la parte de la ruta del archivo.
Después de imprimir la ruta del archivo más reciente, no hay necesidad de continuar: breaksale del bucle sin dejar que se mueva a la segunda ruta del archivo.
Lo único que queda es ejecutarse tailen la ruta del archivo que devuelve esta canalización. Puede haber notado en mi ejemplo que hice esto al encerrar la tubería en una subshell; lo que quizás no haya notado es que incluí la subshell entre comillas dobles. Esto es importante porque al final, incluso con todo este esfuerzo para estar seguro para cualquier nombre de archivo, una expansión de subshell sin comillas podría romper las cosas. Una explicación más detallada está disponible si está interesado. El segundo aspecto importante pero fácil de pasar por alto para la invocación tailes que le proporcioné la opción --antes de expandir el nombre del archivo. Esto instruirátailque no se están especificando más opciones y que todo lo que sigue es un nombre de archivo, lo que hace que sea seguro manejar nombres de archivos que comienzan con -.
tail -f $(find . -type f -exec stat -f "%m {}" {} \;| sort -n | tail -n 1 | cut -d ' ' -f 2)
heady tailno aceptan interruptores para que funcionen con entradas delimitadas por valores nulos". Mi reemplazo para head: … | grep -zm <number> "".
tail `ls -t | head -1`
Si te preocupan los nombres de archivos con espacios,
tail "`ls -t | head -1`"
Puedes usar:
tail $(ls -1t | head -1)
La $()construcción inicia un sub-shell que ejecuta el comando ls -1t(enumerando todos los archivos en orden de tiempo, uno por línea) y canalizándolo head -1para obtener la primera línea (archivo).
La salida de ese comando (el archivo más reciente) se pasa a tailprocesar.
Tenga en cuenta que esto corre el riesgo de obtener un directorio si esa es la entrada de directorio más reciente creada. He usado ese truco en un alias para editar el archivo de registro más reciente (de un conjunto rotativo) en un directorio que contenía solo esos archivos de registro.
-1es necesario, lo lshace por ti cuando está en una tubería. Compara lsy ls|cat, por ejemplo.
En los sistemas POSIX, no hay forma de obtener la entrada del directorio "última creación". Cada entrada de directorio tiene atime, mtimey ctime, a diferencia de Microsoft Windows, ctimeno significa CreationTime, sino "Hora del último cambio de estado".
Entonces, lo mejor que puede obtener es "seguir el último archivo modificado recientemente", que se explica en las otras respuestas. Yo iría por este comando:
cola -f "$ (ls -tr | sed 1q)"
Tenga en cuenta las comillas alrededor del lscomando. Esto hace que el fragmento funcione con casi todos los nombres de archivo.
En zsh:
tail *(.om[1])
Ver: http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Qualifiers , aquí mdenota el tiempo de modificación m[Mwhms][-|+]n, y lo anterior osignifica que está ordenado de una manera (lo Oordena de la otra manera). Los .medios solo archivos regulares. Dentro de los corchetes se [1]recoge el primer elemento. Para elegir tres usos [1,3], para obtener el uso más antiguo [-1].
Es bonito corto y no se usa ls.
Probablemente hay un millón de formas de hacer esto, pero la forma en que lo haría es esta:
tail `ls -t | head -n 1`
Los bits entre los backticks (la comilla como caracteres) se interpretan y el resultado vuelve a la cola.
ls -t #gets the list of files in time order
head -n 1 # returns the first line only
Un simple:
tail -f /path/to/directory/*
funciona bien para mí
El problema es obtener archivos que se generan después de iniciar el comando de cola. Pero si no necesita eso (ya que todas las soluciones anteriores no le importan), el asterisco es una solución más simple, en mi opinión.
Alguien lo publicó y luego lo borró por alguna razón, pero este es el único que funciona, así que ...
tail -f `ls -tr | tail`
lsno es lo más inteligente ...
tail -f `ls -lt | grep -v ^d | head -2 | tail -1 | tr -s " " | cut -f 8 -d " "`
Explicación:
tail "$(ls -1tr|tail -1)"