No shell, como posso tailcriar o arquivo mais recente em um diretório?
No shell, como posso tailcriar 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 .bashrce 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 tailcorretamente. 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 findsuporte -print0como 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 -printfopção find, que me permite especificar quais dados aparecem na saída. Nem todas as versões de findsuporte -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 stattambé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 1impede a findlistagem do conteúdo dos subdiretórios e \! -type dignora 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, sortespera que sua entrada seja registros delimitados por nova linha. Se você possui o GNU, sortpode pedir para que ele espere registros delimitados por nulo, usando a -zopção .; por padrão, sortnã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 sortduas 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 sorto caractere delimitador de campo e -k 1,2especifica os números dos campos: primeiro e segundo ( sortconta 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 sortvocê analisará primeiro 1000000000e depois 0000000000ao solicitar este registro. A -nopção informa sortpara 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é -rpara "reverso". Por padrão, a saída de uma classificação numérica será o número mais baixo primeiro, -raltera-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 heade tailnã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 IFSpara que a lista de arquivos não seja sujeita à divisão de palavras. A seguir, digo readduas 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 recordpara que cada vez que o whileloop 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, readessa técnica não funcionará e você terá pouco recurso.
Sabemos que a recordvariável possui três partes, todas delimitadas por caracteres de ponto. Usando o cututilitário, é possível extrair uma parte deles.
printf '%s' "$record" | cut -d. -f3-
Aqui todo o registro é passado para printfe 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 cutduas coisas: primeiro, com -disso, um delimitador específico para a identificação de campos (como sortno delimitador .é usado). O segundo cuté instruído com -fpara 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 cutirá 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: breaksai do loop sem permitir que ele avance para o segundo caminho do arquivo.
A única coisa que resta é a execução tailno 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átailque 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)
heade tailnã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 -1para obter a primeira linha (arquivo).
A saída desse comando (o arquivo mais recente) é passada para o tailprocessamento.
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.
-1não é necessário, lsfaz isso para você quando está em um cano. Compare lse 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, mtimee ctime, ao contrário do Microsoft Windows, ctimeisso 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 lscomando. 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 mdenota o tempo de modificação m[Mwhms][-|+]ne o anterior osignifica que ele é classificado de uma maneira ( Oclassifica 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`
lsnã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: