En shell, comment puis-je trouver taille dernier fichier créé dans un répertoire?
En shell, comment puis-je trouver taille 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 .bashrcet 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 tailcorrectement. 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 findsupport -print0tel 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 -printfcommutateur find qui me permet de spécifier quelles données apparaissent dans la sortie. Pas toutes les versions de findsupport -printf(ce n'est pas standard) mais GNU find le fait. Si vous vous retrouvez sans, -printfvous devrez vous fier -exec stat {} \;à quel point vous devez abandonner tout espoir de portabilité, ce qui statn'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 1empêche findde lister le contenu des sous-répertoires et \! -type dignore 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, sortattend que son entrée soit des enregistrements délimités par des sauts de ligne. Si vous avez GNU, sortvous pouvez lui demander de s'attendre à des enregistrements séparés par des valeurs nulles à la place en utilisant le -zcommutateur .; pour le standard sortil 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 sortdeux 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; -znrest juste une façon concise de dire -z -n -r). Après cela -t .(l'espace est facultatif) indique sortle caractère délimiteur de champ et -k 1,2spécifie les numéros de champ: premier et deuxième ( sortcompte 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 sortque vous regarderez d'abord 1000000000puis 0000000000lors de la commande de ce disque. L' -noption indique sortd'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é sortest -rpour "inverser". Par défaut, la sortie d'un tri numérique sera en premier les nombres les plus bas, la -rmodifie 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 sorta 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 headet tailn'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 IFSla liste des fichiers pour qu'elle ne soit pas soumise au fractionnement de mots. Ensuite, je dis readdeux 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 recordsorte que chaque fois que la whileboucle itère, il ait un seul horodatage et un seul nom de fichier. Notez qu'il -ds'agit d'une extension GNU; si vous n'avez qu'une norme, readcette technique ne fonctionnera pas et vous avez peu de recours.
Nous savons que la recordvariable comporte trois parties, toutes délimitées par des caractères de période. En utilisant l' cututilitaire, il est possible d'en extraire une partie.
printf '%s' "$record" | cut -d. -f3-
Ici, le dossier entier est transmis à printfet 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 cutdeux choses: d'abord avec -dcela il faut un délimiteur spécifique pour identifier les champs (comme avec sortle délimiteur .utilisé). Le second cutest chargé -fd'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 cutva 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: breakquitte la boucle sans la laisser passer au deuxième chemin de fichier.
La seule chose qui reste est en cours taild' 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 tailest que je lui ai fourni l'option --avant d'étendre le nom du fichier. Cela demanderatailqu'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)
headet tailn'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 -1pour obtenir la première ligne (fichier).
La sortie de cette commande (le fichier le plus récent) est ensuite transmise tailpour ê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.
-1n'est pas nécessaire, lsfait cela pour vous quand il est dans un tuyau. Comparez lset 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, mtimeet ctime, mais contrairement à Microsoft Windows, le ctimene 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 lscommande. 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 mdénote le temps de modification m[Mwhms][-|+]n, et le précédent osignifie qu'il est trié dans un sens (le Otrie 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`
lsn'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: