Eu tenho um diretório cheio de arquivos com nomes como logXX
onde XX é um número hexadecimal maiúsculo de dois caracteres, preenchido com zeros, como:
log00
log01
log02
...
log0A
log0B
log0C
...
log4E
log4F
log50
...
Geralmente haverá menos de 20 ou 30 arquivos no total. A data e a hora em meu sistema específico não são confiáveis (um sistema incorporado sem fontes de tempo NTP ou GPS confiáveis). No entanto, os nomes dos arquivos serão incrementados de forma confiável, conforme mostrado acima.
Desejo grep
examinar todos os arquivos da entrada de log mais recente de um determinado tipo. Esperava cat
reunir os arquivos, como ...
cat /tmp/logs/log* | grep 'WARNING 07 -' | tail -n1
No entanto, ocorreu-me que diferentes versões de bash
ou sh
ou zsh
etc. podem ter ideias diferentes sobre como o *
é expandido.
A man bash
página não diz se a expansão *
seria ou não uma lista alfabética definitivamente ascendente de nomes de arquivos correspondentes. Parece estar aumentando toda vez que eu tentei em todos os sistemas que tenho disponíveis - mas é um comportamento DEFINIDO ou apenas específico da implementação?
Em outras palavras, posso confiar totalmente cat /tmp/logs/log*
para concatenar todos os meus arquivos de log em ordem alfabética?
Responder1
Em todos os shells, os globs são classificados por padrão.Eles já estavam pelo /etc/glob
ajudantechamado pelo shell de Ken Thompson para expandir globs na primeira versão do Unix no início dos anos 70 (e que deu nome aos globs).
Para sh
, o POSIX exige que eles sejam classificados por meio de strcoll()
, ou seja, usando a ordem de classificação na localidade do usuário, embora ls
alguns ainda façam isso por meio de strcmp()
, que é baseado apenas em valores de bytes.
$ dash -c 'echo *'
Log01B log-0D log00 log01 log02 log0A log0B log0C log4E log4F log50 log① log② lóg01
$ bash -c 'echo *'
log① log② log00 log01 lóg01 Log01B log02 log0A log0B log0C log-0D log4E log4F log50
$ zsh -c 'echo *'
log① log② log00 log01 lóg01 Log01B log02 log0A log0B log0C log-0D log4E log4F log50
$ ls
log② log① log00 log01 lóg01 Log01B log02 log0A log0B log0C log-0D log4E log4F log50
$ ls | sort
log②
log①
log00
log01
lóg01
Log01B
log02
log0A
log0B
log0C
log-0D
log4E
log4F
log50
Você pode notar acima que para aqueles shells que fazem a classificação com base na localidade, aqui em um sistema GNU com uma en_GB.UTF-8
localidade, o -
nome dos arquivos é ignorado para classificação (a maioria dos caracteres de pontuação seria). O caso ó
é resolvido de uma forma mais esperada (pelo menos para os britânicos), e o caso é ignorado (exceto quando se trata de decidir empates).
No entanto, você notará algumas inconsistências no log① log②. Isso ocorre porque a ordem de classificação de ① e ② não está definida nas localidades GNU (atualmente; espero que seja corrigida algum dia). Eles classificam da mesma forma, então você obtém resultados aleatórios.
Alterar a localidade afetará a ordem de classificação. Você pode definir a localidade como C para obter uma strcmp()
classificação semelhante a:
$ bash -c 'echo *'
log① log② log00 log01 lóg01 Log01B log02 log0.2 log0A log0B log0C log-0D log4E log4F log50
$ bash -c 'LC_ALL=C; echo *'
Log01B log-0D log0.2 log00 log01 log02 log0A log0B log0C log4E log4F log50 log① log② lóg01
Observe que algumas localidades podem causar algumas confusões, mesmo para strings totalmente ASCII e todos alnum. Como os tchecos (pelo menos em sistemas GNU), onde ch
está umelemento de agrupamentoisso classifica depois h
:
$ LC_ALL=cs_CZ.UTF-8 bash -c 'echo *'
log0Ah log0Bh log0Dh log0Ch
Ou, como apontado por @ninjalj, ainda mais estranhos em localidades húngaras:
$ LC_ALL=hu_HU.UTF-8 bash -c 'echo *'
logX LOGx LOGX logZ LOGz LOGZ logY LOGY LOGy
Em zsh
, você pode escolher a classificação comeliminatórias globais. Por exemplo:
echo *(om) # to sort by modification time
echo *(oL) # to sort by size
echo *(On) # for a *reverse* sort by name
echo *(o+myfunction) # sort using a user-defined function
echo *(N) # to NOT sort
echo *(n) # sort by name, but numerically, and so on.
O tipo numérico echo *(n)
também pode ser habilitado globalmente com a numericglobsort
opção:
$ zsh -c 'echo *'
log① log② log00 log01 lóg01 Log01B log02 log0.2 log0A log0B log0C log-0D log4E log4F log50
$ zsh -o numericglobsort -c 'echo *'
log① log② log00 lóg01 Log01B log0.2 log0A log0B log0C log01 log02 log-0D log4E log4F log50
Se você (como eu) está confuso com essa ordem naquele caso específico (aqui usando minha localidade britânica), consulteaquipara detalhes.
Responder2
A página de manual do bash especifica:
Expansão do nome do caminho
Após a divisão de palavras, a menos que a
-f
opção tenha sido definida, o bash verifica cada palavra em busca dos caracteres*
,?
e[
. Se um desses caracteres aparecer, então a palavra é considerada um padrão e substituída por uma lista ordenada alfabeticamente de nomes de arquivos que correspondem ao padrão [...].
Responder3
A menos que você acione algumas opções de shell muito específicas em alguns shells, a saída será garantidamente a mesma.
A ordem é especificada emo padrão POSIX:
Se o padrão corresponder a qualquer nome de arquivo ou caminho existente, o padrão deverá ser substituído por esses nomes de arquivo e caminho,classificados de acordo com a sequência de agrupamento em vigor na localidade atual. Se esta sequência de agrupamento não tiver uma ordem total de todos os caracteres (consulte XBD LC_COLLATE), quaisquer nomes de arquivos ou nomes de caminho que sejam agrupados igualmente deverão ser comparados byte a byte usando a sequência de agrupamento para o código do idioma POSIX.
Veja tambémCategoria LC_COLLATE na localidade POSIX, que resumidamente diz que se LC_COLLATE=C
, então as coisas são ordenadas na ordem ASCII.
O bash
manual menciona
LC_COLLATE
Esta variável determina a ordem de agrupamento usada ao classificar os resultados da expansão do nome do caminho e determina o comportamento das expressões de intervalo, classes de equivalência e sequências de agrupamento na expansão do nome do caminho e na correspondência de padrões.
ksh93
e zsh
possui redação semelhante, o que me leva a crer que seguem o padrão POSIX nesse aspecto.
Outros shells gostam pdksh
e dash
não dizem nada sobre a classificação dos nomes de arquivos resultantes da globbing de nomes de arquivos. Estou tentado a acreditar que isso significa que eles ainda seguem o mesmo padrão, pelo menos ao usar o código de idioma POSIX. Na minha experiência, não encontrei um shell que fizesse uma classificação abertamente "estranha" de nomes de arquivos ASCII.
Responder4
Se o objetivo principal é classificar os arquivos de entrada por idade, os mais antigos primeiro, você pode escrever
(cd /tmp/logs; cat `ls -rt log*`) | grep whatever
E se logs rotacionados e compactados também estiverem envolvidos:
(cd /tmp/logs; zcat -f `ls -rt log*`) | grep whatever