O caractere curinga * Bash star sempre produz uma lista ordenada (ascendente)?

47

Eu tenho um diretório cheio de arquivos com nomes como logXX , onde XX é um número hexadecimal de dois caracteres, preenchido com zero, como:

log00
log01
log02
...
log0A
log0B
log0C
...
log4E
log4F
log50
...

Geralmente, haverá menos do que 20 ou 30 arquivos no total. A data e a hora no meu sistema particular não são algo em que se possa confiar (um sistema embarcado sem fontes confiáveis de tempo NTP ou GPS). No entanto, os nomes dos arquivos serão incrementados de forma confiável, conforme mostrado acima.

Eu quero grep através de todos os arquivos para a única entrada de log mais recente de um certo tipo, eu esperava cat dos arquivos juntos, como ...

cat /tmp/logs/log* | grep 'WARNING 07 -' | tail -n1

No entanto, ocorreu-me que versões diferentes de bash ou sh ou zsh etc. poderiam ter idéias diferentes sobre como o * é expandido.

A página man bash não diz se a expansão de * seria ou não uma lista em ordem alfabética ascendente de nomes de arquivos correspondentes. Ele parece estar em ascensão toda vez que eu tentei em todos os sistemas que tenho disponíveis para mim - mas é o comportamento DEFINIDO ou apenas a implementação específica?

Em outras palavras, posso confiar totalmente em cat /tmp/logs/log* para concatenar todos os meus arquivos de log juntos em ordem alfabética?

    
por Wossname 31.05.2017 / 15:38

4 respostas

51

Em todos os shells, os globs são classificados por padrão. Eles já estavam no /etc/glob helper chamado pelo shell de Ken Thompson expandir globs na primeira versão do Unix no início dos anos 70 (e que deu o nome a globs).

Para sh , o POSIX exige que eles sejam classificados por meio de strcoll() , que está usando a ordem de classificação na localidade do usuário, como para ls , embora alguns ainda o façam via strcmp() , ou seja com base apenas em valores de byte.

$ 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 os shells que fazem classificação com base no código do idioma, aqui em um sistema GNU com en_GB.UTF-8 locale, o - nos nomes de arquivos é ignorado para classificação (a maioria dos caracteres de pontuação seria). O ó é classificado de uma forma mais esperada (pelo menos para o povo britânico) e o caso é ignorado (exceto quando se trata de decidir os empates).

No entanto, você notará algumas inconsistências para o log① log②. Isso porque a ordem de classificação de ① e ② não é definida em localidades GNU (atualmente, esperamos que seja corrigida algum dia). Eles ordenam o mesmo, então você obtém resultados aleatórios.

A alteração da localidade afetará a ordem de classificação. Você pode definir a localidade como C para obter uma classificação semelhante a strcmp() :

$ 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 até mesmo para cadeias all-ASCII all-alnum. Como os tchecos (pelo menos em sistemas GNU), em que ch é um elemento de agrupamento que classifica após h :

$ LC_ALL=cs_CZ.UTF-8 bash -c 'echo *'
log0Ah log0Bh log0Dh log0Ch

Ou, como apontado por @ninjalj, até mais estranhos em locais húngaros:

$ 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 com os qualificadores da glob. 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.

A classificação numérica de echo *(n) também pode ser ativada globalmente com a opção numericglobsort :

$ 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 estava) está confuso com essa ordem naquela instância específica (aqui usando minha localidade britânica), consulte aqui para detalhes.

    
por 01.06.2017 / 10:26
38

A página man do bash especifica:

Pathname Expansion

After word splitting, unless the -f option has been set, bash scans each word for the characters *, ?, and [. If one of these characters appears, then the word is regarded as a pattern, and replaced with an alphabetically sorted list of filenames matching the pattern […].

    
por 31.05.2017 / 15:59
30

A menos que você ative algumas opções de shell muito específicas em alguns shells, a saída é garantida para ser a mesma.

A encomenda está especificada no padrão POSIX :

If the pattern matches any existing filenames or pathnames, the pattern shall be replaced with those filenames and pathnames, sorted according to the collating sequence in effect in the current locale. If this collating sequence does not have a total ordering of all characters (see XBD LC_COLLATE), any filenames or pathnames that collate equally should be further compared byte-by-byte using the collating sequence for the POSIX locale.

Veja também LC_COLLATE Categoria no POSIX Locale , que em resumo diz que se LC_COLLATE=C , então as coisas são ordenadas em ordem ASCII.

O manual bash menciona

LC_COLLATE

This variable determines the collation order used when sorting the results of pathname expansion, and determines the behavior of range expressions, equivalence classes, and collating sequences within pathname expansion and pattern matching.

ksh93 e zsh tem uma redação semelhante, o que me leva a acreditar que eles seguem o padrão POSIX a esse respeito.

Outros shells, como pdksh e dash , não dizem nada sobre a classificação dos nomes de arquivos resultantes da globalização de nomes de arquivos. Estou tentado a acreditar que isso significa que eles ainda seguem o mesmo padrão, pelo menos quando usam o código do idioma POSIX. Na minha experiência, eu não encontrei um shell que fizesse qualquer classificação "estranha" de nomes de arquivos ASCII.

    
por 31.05.2017 / 15:56
3

Se o objetivo principal é classificar os arquivos de entrada por idade, o mais antigo 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
    
por 31.05.2017 / 20:48