Como encontrar arquivos em subdiretórios e classificá-los por nome de arquivo em um único comando?

9

Resultado de uma localização normal usando find . ! -path "./build*" -name "*.txt":

./tool/001-sub.txt
./tool/000-main.txt
./zo/001-int.txt
./zo/id/002-and.txt
./as/002-mod.txt

e quando classificado com sort -n:

./as/002-mod.txt
./tool/000-main.txt
./tool/001-sub.txt
./zo/001-int.txt
./zo/id/002-and.txt

no entanto, a saída desejada é:

./tool/000-main.txt
./zo/001-int.txt
./tool/001-sub.txt
./zo/id/002-and.txt
./as/002-mod.txt

o que significa que a saída é classificada com base apenas no nome do arquivo , mas as informações da pasta devem ser mantidas como parte da saída.

Editar : Tornar o exemplo mais complicado, pois a estrutura do subdiretório pode incluir mais de um nível.

unode
fonte
2
Veja esta pergunta que eu fiz no SO: stackoverflow.com/questions/3222810/…
camh 23/05/11
@camh - se possível, gostaria de usar apenas comandos unix. De qualquer forma, minha pergunta é praticamente uma duplicata sua. Você pode transferir a melhor solução para este segmento (mantenha um link para o original de qualquer maneira) para que eu possa marcar como a solução?
unode 23/05
Se o @Shawn fizer as alterações sugeridas no meu comentário (use em -printfvez de awk), acho que essa é a melhor solução. Eu refiz a minha implementação original para usar esse método.
Camh

Respostas:

9

Você precisa classificar pelo último campo (considerando /como um separador de campos). Infelizmente, não consigo pensar em uma ferramenta que possa fazer isso quando o número de campos varia (se ao menos sort -kpudéssemos assumir valores negativos).

Para contornar isso, você terá que fazer uma decoração-classificar-undecorate. Ou seja, pegue o nome do arquivo e coloque-o no início, seguido por um separador de campos, faça uma classificação e remova a primeira coluna e separador de campos.

find . ! -path "./build*" -name "*.txt" |\
    awk -vFS=/ -vOFS=/ '{ print $NF,$0 }' |\
    sort -n -t / |\
    cut -f2- -d/

Esse awkcomando diz que o separador de campo FS está definido como /; isso afeta a maneira como ele lê os campos. O separador de campos de saída OFS também está definido como /; isso afeta a maneira como imprime registros. A próxima instrução diz imprime a última coluna ( NFé o número de campos no registro, portanto também é o índice do último campo), bem como o registro inteiro ( $0é o registro inteiro); irá imprimi-los com o OFS entre eles. Em seguida, a lista é sorteditada, tratando /como separador de campos - já que temos o nome do arquivo primeiro no registro, ele será classificado por isso. Em seguida, as cutimpressões são impressas apenas nos campos 2 até o final, tratando novamente /como o separador de campos.

Shawn J. Goff
fonte
3
Uma vez que este é com find (1), você pode pular a parte awk e uso-printf '%f/%p\n'
CAMH
de fato, nossa configuração é um pouco mais complicada. Inclui profundidades de subdiretação variáveis. Editou a pergunta para refletir esse fato. Peço desculpas por não incluir isso no começo.
unode 23/05
11
@Unode: a solução da Shawn lida com profundidade variável muito bem, é a solução canônica para esse problema (até pequenas variações).
Gilles 'SO- stop be evil'
4

Eu usaria os arquivos '-printf' para gerar o nome e o caminho, classificar por nome e cortar o nome em uma última etapa. '###' é apenas um marcador para ajudar no corte.

find -name "*.txt" -printf "%f###%p\n" | sort -n | sed 's/.*###//'

% f imprime o nome do arquivo,% p o caminho inteiro.

Simplifiquei o comando find para colocá-lo em uma linha, é claro que você deixaria a ! -path "./build*"peça.

Usuário desconhecido
fonte
3

No zsh ≥4.3.10:

print -l -- **/*.txt~build*(oe\''REPLY=${REPLY:t}'\')
  • **/*.txtcorresponde *.txtno diretório atual e seus subdiretórios recursivamente .
  • ~build* exclui correspondências cujo texto começa com build*(como ! -path './build*'). (Você precisa setopt extended_globprimeiro.)
  • (oe\''…'\')é um qualificador glob de classificação . REPLY=…constrói a sequência a ser classificada a partir da sequência a ser retornada.
  • ${REPLY:t}é o nome da base ("cauda") do caminho.
Gilles 'SO- parar de ser mau'
fonte
Muita magia concatenada. Interessante, mas estamos limitados à sintaxe sh. +1
unode 23/05