En shell, comment puis-je trouver tail
le dernier fichier créé dans un répertoire?
En shell, comment puis-je trouver tail
le dernier fichier créé dans un répertoire?
Réponses:
Ne pas analyser la sortie de ls! L'analyse de la sortie de ls est difficile et peu fiable .
Si vous devez le faire, je vous recommande d'utiliser find. À l'origine, j'avais ici un exemple simple simplement pour vous donner l'essentiel de la solution, mais comme cette réponse semble quelque peu populaire, j'ai décidé de la réviser pour fournir une version qui est sûre à copier / coller et à utiliser avec toutes les entrées. Êtes-vous assis confortablement? Nous allons commencer avec un oneliner qui vous donnera le dernier fichier dans le répertoire courant:
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)"
Pas tout à fait un oneliner maintenant, n'est-ce pas? Le voici à nouveau comme une fonction shell et formaté pour une lecture plus facile:
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
}
Et maintenant que comme oneliner:
tail -- "$(latest-file-in-directory)"
Si tout le reste échoue, vous pouvez inclure la fonction ci-dessus dans votre .bashrc
et considérer le problème résolu, avec une mise en garde. Si vous vouliez simplement faire le travail, vous n'avez pas besoin de lire plus loin.
La mise en garde avec ceci est qu'un nom de fichier se terminant par une ou plusieurs nouvelles lignes ne sera toujours pas transmis tail
correctement. Contourner ce problème est compliqué et je considère suffisant que si un tel nom de fichier malveillant est rencontré, le comportement relativement sûr de rencontrer une erreur "No such file" se produira au lieu de quelque chose de plus dangereux.
Pour les curieux, c'est l'explication fastidieuse de son fonctionnement, pourquoi il est sûr et pourquoi d'autres méthodes ne le sont probablement pas.
Tout d'abord, le seul octet qui soit sûr pour délimiter les chemins de fichiers est nul car c'est le seul octet universellement interdit dans les chemins de fichiers sur les systèmes Unix. Il est important lors de la gestion d'une liste de chemins de fichiers de n'utiliser que null comme délimiteur et, lors de la remise d'un seul chemin de fichier d'un programme à un autre, de le faire d'une manière qui ne s'étouffe pas sur des octets arbitraires. Il existe de nombreuses façons apparemment correctes de résoudre ce problème et d'autres qui échouent en supposant (même accidentellement) que les noms de fichiers n'auront ni nouvelles lignes ni espaces. Aucune de ces hypothèses n'est sûre.
Pour les besoins d'aujourd'hui, la première étape consiste à obtenir une liste de fichiers délimités par des valeurs nulles. C'est assez facile si vous avez un find
support -print0
tel que GNU:
find . -print0
Mais cette liste ne nous dit toujours pas laquelle est la plus récente, nous devons donc inclure cette information. Je choisis d'utiliser le -printf
commutateur find qui me permet de spécifier quelles données apparaissent dans la sortie. Pas toutes les versions de find
support -printf
(ce n'est pas standard) mais GNU find le fait. Si vous vous retrouvez sans, -printf
vous devrez vous fier -exec stat {} \;
à quel point vous devez abandonner tout espoir de portabilité, ce qui stat
n'est pas standard non plus. Pour l'instant, je vais continuer en supposant que vous avez des outils GNU.
find . -printf '%T@.%p\0'
Ici, je demande le format printf %T@
qui est le temps de modification en secondes depuis le début de l'époque Unix suivi d'un point puis suivi d'un nombre indiquant des fractions de seconde. J'ajoute à cela une autre période, puis %p
(qui est le chemin d'accès complet au fichier) avant de terminer avec un octet nul.
Maintenant j'ai
find . -maxdepth 1 \! -type d -printf '%T@.%p\0'
Cela peut aller de soi, mais pour être complet, il -maxdepth 1
empêche find
de lister le contenu des sous-répertoires et \! -type d
ignore les répertoires que vous ne voudrez probablement pas tail
. Jusqu'à présent, j'ai des fichiers dans le répertoire actuel avec des informations sur l'heure de modification, alors maintenant je dois trier par heure de modification.
Par défaut, sort
attend que son entrée soit des enregistrements délimités par des sauts de ligne. Si vous avez GNU, sort
vous pouvez lui demander de s'attendre à des enregistrements séparés par des valeurs nulles à la place en utilisant le -z
commutateur .; pour le standard sort
il n'y a pas de solution. Je ne suis intéressé que par le tri par les deux premiers nombres (secondes et fractions de seconde) et je ne veux pas trier par le nom de fichier réel, donc je dis sort
deux choses: d'abord, il doit considérer le point ( .
) comme un délimiteur de champ et deuxièmement, il ne doit utiliser les premier et deuxième champs que pour déterminer comment trier les enregistrements.
| sort -znr -t. -k1,2
Tout d'abord, je regroupe trois options courtes qui ne prennent aucune valeur ensemble; -znr
est juste une façon concise de dire -z -n -r
). Après cela -t .
(l'espace est facultatif) indique sort
le caractère délimiteur de champ et -k 1,2
spécifie les numéros de champ: premier et deuxième ( sort
compte les champs à partir de un, pas de zéro). N'oubliez pas qu'un exemple d'enregistrement pour le répertoire actuel ressemblerait à:
1000000000.0000000000../some-file-name
Cela signifie sort
que vous regarderez d'abord 1000000000
puis 0000000000
lors de la commande de ce disque. L' -n
option indique sort
d'utiliser la comparaison numérique lors de la comparaison de ces valeurs, car les deux valeurs sont des nombres. Cela peut ne pas être important car les nombres sont de longueur fixe mais cela ne fait pas de mal.
L'autre commutateur donné sort
est -r
pour "inverser". Par défaut, la sortie d'un tri numérique sera en premier les nombres les plus bas, la -r
modifie pour qu'elle répertorie les derniers nombres en bas et les premiers en premier. Étant donné que ces chiffres sont des horodatages plus élevés, cela signifie que les nouveaux sont plus récents, ce qui place le plus récent enregistrement au début de la liste.
Comme la liste des chemins de fichiers qui en émerge sort
a maintenant la réponse souhaitée que nous recherchons tout en haut. Reste à trouver un moyen de supprimer les autres enregistrements et de supprimer l'horodatage. Malheureusement, même GNU head
et tail
n'acceptent pas les commutateurs pour les faire fonctionner sur une entrée délimitée par des valeurs nulles. Au lieu de cela, j'utilise une boucle while comme une sorte de pauvre head
.
| while IFS= read -r -d '' record
Tout d'abord, je désactive IFS
la liste des fichiers pour qu'elle ne soit pas soumise au fractionnement de mots. Ensuite, je dis read
deux choses: n'interprétez pas les séquences d'échappement dans l'entrée ( -r
) et l'entrée est délimitée par un octet nul ( -d
); ici, la chaîne vide ''
est utilisée pour indiquer "pas de délimiteur" aka délimité par null. Chaque enregistrement sera lu dans la variable de record
sorte que chaque fois que la while
boucle itère, il ait un seul horodatage et un seul nom de fichier. Notez qu'il -d
s'agit d'une extension GNU; si vous n'avez qu'une norme, read
cette technique ne fonctionnera pas et vous avez peu de recours.
Nous savons que la record
variable comporte trois parties, toutes délimitées par des caractères de période. En utilisant l' cut
utilitaire, il est possible d'en extraire une partie.
printf '%s' "$record" | cut -d. -f3-
Ici, le dossier entier est transmis à printf
et de là canalisé vers cut
; en bash, vous pouvez simplifier davantage en utilisant une chaîne ici pour cut -d. -3f- <<<"$record"
de meilleures performances. Nous disons cut
deux choses: d'abord avec -d
cela il faut un délimiteur spécifique pour identifier les champs (comme avec sort
le délimiteur .
utilisé). Le second cut
est chargé -f
d'imprimer uniquement les valeurs de champs spécifiques; la liste des champs est donnée sous la forme d'une plage 3-
qui indique la valeur du troisième champ et de tous les champs suivants. Cela signifie que cut
va lire et ignorer tout jusqu'à et y compris la seconde .
qu'il trouve dans l'enregistrement, puis imprimer le reste, qui est la partie du chemin d'accès au fichier.
Après avoir imprimé le dernier chemin de fichier, il n'est pas nécessaire de continuer: break
quitte la boucle sans la laisser passer au deuxième chemin de fichier.
La seule chose qui reste est en cours tail
d' exécution sur le chemin de fichier renvoyé par ce pipeline. Vous avez peut-être remarqué dans mon exemple que j'ai fait cela en enfermant le pipeline dans un sous-shell; ce que vous n'avez peut-être pas remarqué, c'est que j'ai mis le sous-shell entre guillemets. Ceci est important car au final, même avec tous ces efforts pour être sûr pour tous les noms de fichiers, une extension de sous-shell non citée pourrait toujours casser les choses. Une explication plus détaillée est disponible si vous êtes intéressé. Le deuxième aspect important mais facilement ignoré de l'invocation de tail
est que je lui ai fourni l'option --
avant d'étendre le nom du fichier. Cela demanderatail
qu'aucune autre option n'est spécifiée et que tout ce qui suit est un nom de fichier, ce qui permet de gérer en toute sécurité les noms de fichier commençant par -
.
tail -f $(find . -type f -exec stat -f "%m {}" {} \;| sort -n | tail -n 1 | cut -d ' ' -f 2)
head
et tail
n'acceptent pas les commutateurs pour les faire fonctionner sur une entrée délimitée par des valeurs nulles." Mon remplacement pour head
: … | grep -zm <number> ""
.
tail `ls -t | head -1`
Si vous vous inquiétez des noms de fichiers avec des espaces,
tail "`ls -t | head -1`"
Vous pouvez utiliser:
tail $(ls -1t | head -1)
La $()
construction démarre un sous-shell qui exécute la commande ls -1t
(répertoriant tous les fichiers dans l'ordre temporel, un par ligne) et les redirige head -1
pour obtenir la première ligne (fichier).
La sortie de cette commande (le fichier le plus récent) est ensuite transmise tail
pour être traitée.
Gardez à l'esprit que cela court le risque d'obtenir un répertoire s'il s'agit de l'entrée de répertoire la plus récente créée. J'ai utilisé cette astuce dans un alias pour modifier le fichier journal le plus récent (à partir d'un ensemble rotatif) dans un répertoire qui ne contenait que ces fichiers journaux.
-1
n'est pas nécessaire, ls
fait cela pour vous quand il est dans un tuyau. Comparez ls
et ls|cat
, par exemple.
Sur les systèmes POSIX, il n’existe aucun moyen d’obtenir l’entrée de répertoire "créée en dernier". Chaque entrée de répertoire a atime
, mtime
et ctime
, mais contrairement à Microsoft Windows, le ctime
ne signifie pas CreationTime, mais "Heure du dernier changement d'état".
Donc, le mieux que vous puissiez obtenir est de "suivre le dernier fichier récemment modifié", ce qui est expliqué dans les autres réponses. J'irais pour cette commande:
tail -f "$ (ls -tr | sed 1q)"
Notez les guillemets autour de la ls
commande. Cela permet à l'extrait de fonctionner avec presque tous les noms de fichiers.
Dans zsh
:
tail *(.om[1])
Voir: http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Qualifiers , ici m
dénote le temps de modification m[Mwhms][-|+]n
, et le précédent o
signifie qu'il est trié dans un sens (le O
trie dans l'autre sens). Cela .
signifie que les fichiers normaux. Dans les parenthèses [1]
choisit le premier élément. Pour en choisir trois [1,3]
, pour obtenir la plus ancienne utilisation [-1]
.
C'est bien court et ne l'utilise pas ls
.
Il y a probablement un million de façons de le faire, mais la façon dont je le ferais est la suivante:
tail `ls -t | head -n 1`
Les bits entre les pointes (les guillemets comme des caractères) sont interprétés et le résultat renvoyé à la queue.
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/*
fonctionne très bien pour moi.
Le problème est d'obtenir les fichiers générés après avoir démarré la commande tail. Mais si vous n'en avez pas besoin (comme toutes les solutions ci-dessus ne s'en soucient pas), l'astérisque est simplement une solution plus simple, l'OMI.
Quelqu'un l'a posté, puis l'a effacé pour une raison quelconque, mais c'est le seul qui fonctionne, alors ...
tail -f `ls -tr | tail`
ls
n'est pas la chose la plus intelligente à faire ...
tail -f `ls -lt | grep -v ^d | head -2 | tail -1 | tr -s " " | cut -f 8 -d " "`
Explication: