No shell, como posso tail
criar o arquivo mais recente em um diretório?
No shell, como posso tail
criar o arquivo mais recente em um diretório?
Respostas:
Você não analisar a saída de ls! Analisar a saída de ls é difícil e não confiável .
Se você deve fazer isso, recomendo usar o find. Originalmente, eu tinha aqui um exemplo simples apenas para fornecer a essência da solução, mas como essa resposta parece um pouco popular, decidi revisar isso para fornecer uma versão que seja segura para copiar / colar e usar com todas as entradas. Você está sentado confortavelmente? Começaremos com um oneliner que fornecerá o arquivo mais recente no diretório atual:
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)"
Não é exatamente um oneliner agora, é? Aqui está novamente como uma função shell e formatada para facilitar a leitura:
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
}
E agora isso como um delineador:
tail -- "$(latest-file-in-directory)"
Se tudo mais falhar, você pode incluir a função acima .bashrc
e considerar o problema resolvido, com uma ressalva. Se você apenas queria fazer o trabalho, não precisa ler mais.
A ressalva disso é que um nome de arquivo que termina em uma ou mais novas linhas ainda não será passado tail
corretamente. A solução do problema é complicada e considero suficiente que, se um nome de arquivo mal-intencionado for encontrado, o comportamento relativamente seguro de encontrar um erro "Nenhum arquivo desse tipo" ocorrerá em vez de algo mais perigoso.
Para os curiosos, essa é a explicação tediosa de como funciona, por que é seguro e por que outros métodos provavelmente não são.
Antes de tudo, o único byte seguro para delimitar os caminhos de arquivo é nulo, pois é o único byte proibido universalmente nos caminhos de arquivo nos sistemas Unix. É importante ao manipular qualquer lista de caminhos de arquivo usar nulo apenas como delimitador e, ao entregar um único caminho de arquivo de um programa para outro, fazê-lo de uma maneira que não engasgue com bytes arbitrários. Existem muitas maneiras aparentemente corretas de resolver esse e outros problemas que falham ao assumir (mesmo que acidentalmente) que os nomes de arquivos não terão novas linhas ou espaços. Nenhuma das suposições é segura.
Para os propósitos de hoje, a primeira etapa é obter uma lista de arquivos delimitada por nulos fora da localização. Isso é muito fácil se você tiver um find
suporte -print0
como o GNU:
find . -print0
Mas essa lista ainda não nos diz qual é a mais nova, por isso precisamos incluir essas informações. Eu escolho usar a -printf
opção find, que me permite especificar quais dados aparecem na saída. Nem todas as versões de find
suporte -printf
(não é padrão), mas o GNU find sim. Se você se encontrar sem -printf
, precisará confiar em -exec stat {} \;
que ponto deve desistir de toda a esperança de portabilidade, pois isso stat
também não é padrão. Por enquanto, vou seguir assumindo que você tem ferramentas GNU.
find . -printf '%T@.%p\0'
Aqui, estou solicitando o formato printf, %T@
que é o tempo de modificação em segundos desde o início da época do Unix, seguido de um período e depois de um número indicando frações de segundo. Eu adiciono a isso outro período e depois %p
(que é o caminho completo para o arquivo) antes de terminar com um byte nulo.
Agora eu tenho
find . -maxdepth 1 \! -type d -printf '%T@.%p\0'
Pode ser óbvio, mas por uma questão de conclusão, -maxdepth 1
impede a find
listagem do conteúdo dos subdiretórios e \! -type d
ignora os diretórios que você provavelmente não deseja tail
. Até o momento, tenho arquivos no diretório atual com informações de tempo de modificação; agora, preciso classificar por esse horário de modificação.
Por padrão, sort
espera que sua entrada seja registros delimitados por nova linha. Se você possui o GNU, sort
pode pedir para que ele espere registros delimitados por nulo, usando a -z
opção .; por padrão, sort
não há solução. Eu só estou interessado em classificar pelos dois primeiros números (segundos e frações de segundo) e não quero classificar pelo nome do arquivo real, então eu digo sort
duas coisas: primeiro, que ele deve considerar o período ( .
) um delimitador de campo e segundo, que ele deve usar apenas o primeiro e o segundo campos ao considerar como classificar os registros.
| sort -znr -t. -k1,2
Antes de tudo, estou agrupando três opções curtas que não têm valor juntos; -znr
é apenas uma maneira concisa de dizer -z -n -r
). Depois disso -t .
(o espaço é opcional) informa sort
o caractere delimitador de campo e -k 1,2
especifica os números dos campos: primeiro e segundo ( sort
conta os campos de um, não zero). Lembre-se de que um registro de amostra para o diretório atual se pareceria com:
1000000000.0000000000../some-file-name
Isso significa que sort
você analisará primeiro 1000000000
e depois 0000000000
ao solicitar este registro. A -n
opção informa sort
para usar a comparação numérica ao comparar esses valores, porque os dois valores são números. Isso pode não ser importante, pois os números são de comprimento fixo, mas não causam danos.
A outra opção dada sort
é -r
para "reverso". Por padrão, a saída de uma classificação numérica será o número mais baixo primeiro, -r
altera-o para listar os números mais baixos por último e os números mais altos primeiro. Como esses números são de data e hora mais altos, será mais recente e isso colocará o registro mais recente no início da lista.
À medida que a lista de caminhos de arquivos surge sort
, agora ela tem a resposta desejada que estamos procurando no topo. O que resta é encontrar uma maneira de descartar os outros registros e retirar o registro de data e hora. Infelizmente, mesmo o GNU head
e tail
não aceita switches para fazê-los operar em entradas delimitadas por nulos. Em vez disso, uso um loop while como uma espécie de homem pobre head
.
| while IFS= read -r -d '' record
Primeiro, desmarco IFS
para que a lista de arquivos não seja sujeita à divisão de palavras. A seguir, digo read
duas coisas: não interprete seqüências de escape na entrada ( -r
) e a entrada é delimitada com um byte nulo ( -d
); aqui a cadeia vazia ''
é usada para indicar "sem delimitador", também conhecido como delimitado por null. Cada registro será lido na variável record
para que cada vez que o while
loop itere, ele tenha um único registro de data e hora e um único nome de arquivo. Observe que -d
é uma extensão GNU; se você tiver apenas um padrão, read
essa técnica não funcionará e você terá pouco recurso.
Sabemos que a record
variável possui três partes, todas delimitadas por caracteres de ponto. Usando o cut
utilitário, é possível extrair uma parte deles.
printf '%s' "$record" | cut -d. -f3-
Aqui todo o registro é passado para printf
e de lá canalizado para cut
; no bash, você pode simplificar ainda mais isso usando uma string here para cut -d. -3f- <<<"$record"
obter melhor desempenho. Dizemos cut
duas coisas: primeiro, com -d
isso, um delimitador específico para a identificação de campos (como sort
no delimitador .
é usado). O segundo cut
é instruído com -f
para imprimir apenas valores de campos específicos; a lista de campos é fornecida como um intervalo 3-
que indica o valor do terceiro campo e de todos os campos seguintes. Isso significa que cut
irá ler e ignorar tudo, inclusive o segundo .
encontrado no registro, e imprimirá o restante, que é a parte do caminho do arquivo.
Depois de imprimir o caminho do arquivo mais recente, não há necessidade de continuar: break
sai do loop sem permitir que ele avance para o segundo caminho do arquivo.
A única coisa que resta é a execução tail
no caminho do arquivo retornado por este pipeline. Você deve ter notado no meu exemplo que eu fiz isso colocando o pipeline em um subshell; o que você pode não ter notado é que coloquei o subshell entre aspas duplas. Isso é importante porque, finalmente, mesmo com todo esse esforço para ser seguro para qualquer nome de arquivo, uma expansão de sub-shell não citada ainda pode quebrar as coisas. Uma explicação mais detalhada está disponível se você estiver interessado. O segundo aspecto importante, mas facilmente esquecido, da invocação de tail
é que forneci a opção --
antes de expandir o nome do arquivo. Isso instruirátail
que não há mais opções sendo especificadas e tudo o que se segue é um nome de arquivo, o que torna seguro manipular os nomes de arquivos que começam com -
.
tail -f $(find . -type f -exec stat -f "%m {}" {} \;| sort -n | tail -n 1 | cut -d ' ' -f 2)
head
e tail
não aceita switches para fazê-los operar em entradas delimitadas por nulos." Meu substituto para head
: … | grep -zm <number> ""
.
tail `ls -t | head -1`
Se você está preocupado com nomes de arquivos com espaços,
tail "`ls -t | head -1`"
Você pode usar:
tail $(ls -1t | head -1)
A $()
construção inicia um sub-shell que executa o comando ls -1t
(listando todos os arquivos na ordem do tempo, um por linha) e canalizando-o head -1
para obter a primeira linha (arquivo).
A saída desse comando (o arquivo mais recente) é passada para o tail
processamento.
Lembre-se de que isso corre o risco de obter um diretório, se essa for a entrada de diretório mais recente criada. Usei esse truque em um alias para editar o arquivo de log mais recente (de um conjunto rotativo) em um diretório que continha apenas esses arquivos de log.
-1
não é necessário, ls
faz isso para você quando está em um cano. Compare ls
e ls|cat
, por exemplo.
Nos sistemas POSIX, não há como obter a entrada de diretório "última criação". Cada entrada de diretório possui atime
, mtime
e ctime
, ao contrário do Microsoft Windows, ctime
isso não significa CreationTime, mas "Hora da última alteração de status".
Portanto, o melhor que você pode obter é "ajustar o último arquivo modificado recentemente", o que é explicado nas outras respostas. Eu iria para este comando:
tail -f "$ (ls -tr | sed 1q)"
Observe as aspas ao redor do ls
comando. Isso faz com que o trecho funcione com quase todos os nomes de arquivos.
Em zsh
:
tail *(.om[1])
Consulte: http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Qualifiers , aqui m
denota o tempo de modificação m[Mwhms][-|+]n
e o anterior o
significa que ele é classificado de uma maneira ( O
classifica da outra maneira). Os .
meios apenas arquivos regulares. Dentro dos colchetes, [1]
escolhe o primeiro item. Para escolher três usos [1,3]
, para obter o uso mais antigo [-1]
.
É bom curto e não usa ls
.
Provavelmente existem milhões de maneiras de fazer isso, mas a maneira como eu faria é:
tail `ls -t | head -n 1`
Os bits entre os backticks (os caracteres semelhantes a aspas) são interpretados e o resultado retornado ao final.
ls -t #gets the list of files in time order
head -n 1 # returns the first line only
Um simples:
tail -f /path/to/directory/*
funciona muito bem para mim.
O problema é obter arquivos gerados depois que você iniciou o comando tail. Mas se você não precisar disso (como todas as soluções acima não se importam com isso), o asterisco é apenas uma solução mais simples, IMO.
Alguém postou e apagou por algum motivo, mas este é o único que funciona, então ...
tail -f `ls -tr | tail`
ls
não é a coisa mais inteligente a fazer ...
tail -f `ls -lt | grep -v ^d | head -2 | tail -1 | tr -s " " | cut -f 8 -d " "`
Explicação: