Suponha que eu tenha uma lista de nomes de caminhos de arquivos armazenados em uma matriz
filearray=("dir1/0010.pdf" "dir2/0003.pdf" "dir3/0040.pdf" )
Eu quero classificar os elementos na matriz de acordo com os nomes de base dos nomes de arquivos, em ordem numérica
sortedfilearray=("dir2/0003.pdf" "dir1/0010.pdf" "dir3/0040.pdf")
Como eu posso fazer isso?
Só posso classificar as partes do nome da base:
basenames=()
for file in "${filearray[@]}"
do
filename=${file##*/}
basenames+=(${filename%.*})
done
sortedbasenamearr=($(printf '%s\n' "${basenames[@]}" | sort -n))
Eu estou pensando sobre
- criando uma matriz associativa cujas chaves são os nomes de base e os valores são os nomes de caminho, portanto, o acesso aos nomes de caminho é sempre feito via nomes de base.
- criando outra matriz apenas para nomes de base e aplique-
sort
a à matriz de nome de base.
Obrigado.
dir1
dir2
são apenas inventadas e, na verdade, são nomes de caminho arbitrários.Respostas:
Ao contrário do ksh ou zsh, o bash não tem suporte interno para classificar matrizes ou listas de cadeias arbitrárias. Ele pode classificar globs ou a saída de
alias
orset
ortypeset
(embora os últimos 3 não estejam na ordem de classificação do código do idioma do usuário), mas isso não pode ser usado praticamente aqui.Não há nada no baú da ferramenta POSIX que possa classificar prontamente listas arbitrárias de seqüências de caracteres¹ (
sort
classifica linhas, apenas sequências curtas (LINE_MAX geralmente são mais curtas que PATH_MAX) de caracteres diferentes de NUL e newline, enquanto os caminhos de arquivo são sequências de bytes não vazias, que 0).Portanto, embora você possa implementar seu próprio algoritmo de classificação em
awk
(usando o<
operador de comparação de cadeias) ou mesmobash
(usando[[ < ]]
), para caminhos arbitrários embash
, de maneira portátil, o mais fácil pode ser o deperl
:Com
bash4.4+
, você pode fazer:Isso dá uma
strcmp()
ordem semelhante. Para uma ordem baseada em regras de agrupamento da localidade como em bolhas ou a saída dels
, adicione um-Mlocale
argumento paraperl
. Para classificação numérica (mais parecida com o GNUsort -g
, pois suporta números como+3
,1.2e-5
e não milhares de separadores, embora não hexadimais), use em<=>
vez decmp
(e novamente-Mlocale
para que a marca decimal do usuário seja honrada como para osort
comando).Você ficará limitado pelo tamanho máximo de argumentos a um comando. Para evitar isso, você pode passar a lista de arquivos para
perl
seu stdin em vez de via argumentos:Nas versões mais antigas do
bash
, você pode usar umwhile IFS= read -rd ''
loop em vez dereadarray -d ''
ou obterperl
a lista de caminhos citados corretamente, para que possa transmiti-loeval "array=($(perl...))"
.Com
zsh
, você pode falsificar uma expansão global para a qual você pode definir uma ordem de classificação:Com
reply=($filearray)
isso, forçamos a expansão glob (que inicialmente era justa/
) a ser os elementos da matriz. Em seguida, definimos a ordem de classificação a ser baseada na cauda do nome do arquivo.Para uma
strcmp()
ordem semelhante, fixe o código do idioma para C. Para a classificação numérica (semelhante ao GNUsort -V
, não osort -n
que faz uma diferença significativa ao comparar1.4
e1.23
(em locais onde.
é a marca decimal), por exemplo), adicione on
qualificador glob.Em vez de
oe{expression}
, você também pode usar uma função para definir uma ordem de classificação como:ou mais avançados como:
(so
a/foo2bar3.pdf
(2,3 números) classifica depois deb/bar1foo3.pdf
(1,3) mas antes dec/baz2zzz10.pdf
(2,10)) e usa como:Obviamente, eles podem ser aplicados em globs reais, pois é para isso que eles se destinam principalmente. Por exemplo, para uma lista de
pdf
arquivos em qualquer diretório, classificados por nome de base / cauda:¹ Se a
strcmp()
classificação baseada em uma é aceitável, e para cadeias curtas, você pode transformar as cadeias em sua codificação hexadecimalawk
antes de passar parasort
e transformar novamente após a classificação.fonte
sort
no GNU coreutils permite separador de campo personalizado e chave. Você define/
como separador de campos e classifica com base no segundo campo para classificar no nome da base, em vez do caminho inteiro.printf "%s\n" "${filearray[@]}" | sort -t/ -k2
vai produzirfonte
sort
, não uma extensão GNU. Isso funcionará se os caminhos tiverem o mesmo comprimento.some/long/path/0011.pdf
? Tanto quanto posso ver na sua página de manual,sort
does não contém nenhuma opção para classificar pelo último campo.Classificação com expressão gawk (suportada pelo bash 's
readarray
):Exemplo de matriz de nomes de arquivos contendo espaços em branco :
A saída:
Acessando item único:
Isso pressupõe que nenhum caminho de arquivo contenha caracteres de nova linha. Observe que a classificação numérica dos valores
@val_num_asc
se aplica apenas à parte numérica inicial da chave (nenhum neste exemplo) com fallback para comparação lexical (com base nastrcmp()
ordem de classificação do código do idioma), para vínculos.fonte
A classificação de nomes de arquivos com novas linhas em seus nomes causará problemas na
sort
etapa.Ele gera uma
/
lista delimitada comawk
o nome da base na primeira coluna e o caminho completo como as colunas restantes:É isso que é classificado e
cut
é usado para remover a primeira/
coluna delimitada. O resultado é transformado em uma novabash
matriz.fonte
/some/dir/
.a/x.c++ b/x.c-- c/x.c++
ela seria classificada nessa ordem, embora seja-
classificada antes+
porque-
,+
e/
o peso principal de IGNORE (portanto, a comparaçãox.c++/a/x.c++
com asx.c--/b/x.c++
primeiras compara excaxc
contraxcbxc
, e somente em caso de empate, os outros pesos (onde-
vem antes+
) seria considerado./x/
vez de/
, mas isso não resolveria o caso em que, no local C em sistemas baseados em ASCII,a/foo
classificaria depois,a/foo.txt
por exemplo, porque/
classifica depois.
.Como "
dir1
edir2
são nomes de caminho arbitrários", não podemos contar com eles consistindo em um único diretório (ou no mesmo número de diretórios). Portanto, precisamos converter a última barra nos nomes dos caminhos para algo que não ocorra em nenhum outro lugar no nome do caminho. Supondo que o caractere@
não ocorra nos seus dados, você pode classificar por nome da base assim:O primeiro
sed
comando substitui a última barra em cada nome de caminho pelo separador escolhido, o segundo reverte a alteração. (Para simplificar, estou assumindo que os nomes de caminho podem ser entregues um por linha. Se eles estiverem em uma variável de shell, converta-os primeiro para o formato de um por linha.)fonte
cat pathnames | sed 's|\(.*\)/|\1'$'\4''|' | sort -t$'\4' -k+2nr | sed 's|'$'\4''|/|'
. (Eu apenas peguei\4
da tabela ASCII Aparentemente "FIM DO TEXTO".?)\4
é^D
(control-D). A menos que você digite no terminal, é um caractere de controle comum. Em outras palavras, seguro de usar dessa maneira.Solução curta (e um tanto rápida): anexando o índice da matriz aos nomes dos arquivos e ordenando-os, podemos criar mais tarde uma versão classificada com base nas indicações ordenadas.
Essa solução precisa apenas dos bash do bash, bem como do
sort
binário, e também funciona com todos os nomes de arquivos que não incluem um\n
caractere de nova linha .Para cada arquivo, repetimos o nome da base com o índice inicial anexado da seguinte forma:
e depois enviado
sort -n
.Depois, iteramos sobre as linhas de saída, extraímos o índice antigo com expansão de variável bash
${line##* }
e inserimos esse elemento no final da nova matriz.fonte
Isso classifica anexando os nomes de caminho do arquivo com o nome da base, classificando-o numericamente e removendo o nome da base da frente da string:
Seria mais eficiente se você tivesse os nomes de arquivos em uma lista que poderia ser passada diretamente através de um canal, e não como uma matriz de shell, porque o trabalho real é realizado pelo
sed | sort | sed
estrutura, mas isso é suficiente.Eu me deparei com essa técnica ao codificar em Perl; naquela língua, era conhecida como Transformação Schwartziana .
No Bash, a transformação, conforme indicado aqui no meu código, falhará se você tiver dados não numéricos no nome de base do arquivo. No Perl, poderia ser codificado com muito mais segurança.
fonte
$@
ou$*
de argumentos de linha de comando para executar um scriptPara nomes de arquivos com profundidade igual.
Explicação
As informações são retiradas do homem do tipo.
A impressão resultante da matriz
fonte