Tenho vários arquivos CSV de 1MB a 6GB gerados por inotify
script com uma lista de eventos formatados como:
timestamp;fullpath;event;size
.
Esses arquivos são formatados assim:
timestamp;fullpath;event;size
1521540649.02;/home/workdir/ScienceXMLIn/config.cfg;IN_OPEN;2324
1521540649.02;/home/workdir/ScienceXMLIn/config.cfg;IN_ACCESS;2324
1521540649.02;/home/workdir/ScienceXMLIn/config.cfg;IN_CLOSE_NOWRITE;2324
1521540649.02;/home/workdir/quad_list_14.json;IN_OPEN;2160
1521540649.03;/home/workdir/quad_list_14.json;IN_ACCESS;2160
1521540649.03;/home/workdir/quad_list_14.json;IN_CLOSE_NOWRITE;2160
1521540649.03;/home/workdir/ScienceXMLIn/masterbias_list.asc;IN_OPEN;70
1521540649.03;/home/workdir/ScienceXMLIn/masterbias_list.asc.1;IN_OPEN;80
1521540649.03;/home/workdir/ScienceXMLIn/masterbias_list.asc.2;IN_OPEN;70
1521540649.03;/home/workdir/otherfolder/quad_list_14.json;IN_OPEN;2160
1521540649.03;/home/workdir/otherfolder/quad_list_14.json;IN_CLOSE_NOWRITE;2160
Meu objetivo é identificar o arquivo com o mesmo nome que aparece em pastas diferentes.
Neste exemplo, o arquivo quad_list_14.json
aparece nos formatos /home/workdir/otherfolder
e /home/workdir/
.
Minha saída desejada é simples apenas a lista de arquivos que aparece em mais de uma pasta, neste caso ficaria assim:
quad_list_14.json
Para fazer isso, escrevi este pequeno trecho de código:
#this line cut the file to only get unique filepath
PATHLIST=$(cut -d';' -f 2 ${1} | sort -u)
FILENAMELIST=""
#this loop build a list of basename from the list of filepath
for path in ${PATHLIST}
do
FILENAMELIST="$(basename "${path}")
${FILENAMELIST}"
done
#once the list is build, I simply find the duplicates with uniq -d as the list is already sorted
echo "${FILENAMELIST}" | sort | uniq -d
Não use este código em casa, é terrível, eu deveria substituir este script por um onliner como este:
#this get all file path, sort them and only keep unique entry then
#remove the path to get the basename of the file
#and finally sort and output duplicates entry.
cut -d';' -f 2 ${1} | sort -u | grep -o '[^/]*$' | sort | uniq -d
Meu problema, porém, permanece e muitos arquivos e o mais curto leva 0,5 segundo, mas o mais longo leva 45 segundos em um SSD (e meu disco de produção não será tão rápido) para encontrar o nome do arquivo duplicado em uma pasta diferente.
Preciso melhorar esse código para torná-lo mais eficiente. Minha única limitação é que não consigo carregar totalmente os arquivos na RAM.
O seguinte script AWK deve fazer o truque, sem usar muita memória:
Ele extrai o caminho e o nome de cada arquivo e armazena o último caminho visto para cada nome. Se já havia visto outro caminho, ele gera o nome, a menos que já o tenha exibido.
O principal problema com seu código é que você está coletando todos os nomes de caminho em uma variável e, em seguida, fazendo um loop para chamar
basename
. Isso torna lento.O loop também é executado sobre a variável expansion sem aspas
${PATHLIST}
, o que seria imprudente se os nomes de caminho contivessem espaços ou caracteres de globbing de shell. Embash
(ou em outros shells que o suportam), seria usado um array.Sugestão:
O primeiro
sed
escolhe os nomes dos caminhos (e descarta a linha do cabeçalho). Isso também pode ser escrito comoawk -F';' 'NR > 1 { print $2 }' file.csv
, ou comotail -n +2 file.csv | cut -d ';' -f 2
.O
sort -u
nos fornece nomes de caminho exclusivos e o seguintesed
nos fornece os nomes de base. O finalsort
comuniq -d
no final nos diz quais nomes de base são duplicados.O último
sed 's#.*/##'
que fornece os nomes de base é uma reminiscência da expansão do parâmetro${pathname##*/}
que é equivalente a$( basename "$pathname" )
. Ele apenas exclui tudo até e incluindo o último/
na string.A principal diferença do seu código é que, em vez do loop que chama
basename
várias vezes, um únicosed
é usado para produzir os nomes de base de uma lista de nomes de caminho.Alternativa para olhar apenas as
IN_OPEN
entradas:Obrigado a vocês dois por suas respostas e obrigado Isaac pelos comentários.
Peguei todo o seu código e coloquei em um script
stephen.awk
kusa.sh
eisaac.sh
depois executei um pequeno benchmark como este:Com o comando
time
eu os comparo e aqui estão os resultados:stephen.awk
stephen.awk : atualizado com /IN_OPEN/ antes do segundo bloco
kusa.sh
Atualizar com filtro em
IN_OPEN
:Nota lateral:
embora correto, eu tinha muitas linhas em branco geradas com
sed
, seu script era o único assim.isaac.sh
Com filtro ativado
IN_OPEN
:meu roteiro
@Stephen você claramente venceu esta, com uma redução de tempo impressionante em um fator de 2,5.
Embora depois de pensar um pouco mais, tive outra ideia, e se eu apenas olhar para o evento OPEN file, isso reduziria a complexidade e você não deveria acessar um arquivo ou escrevê-lo sem abri-lo primeiro, então fiz isso:
Com esta única modificação que me deu o mesmo resultado, acabo com este
time
valor:E tenho certeza de que há muitas outras coisas que eu poderia fazer