While loop demorando mais tempo para concluir

2

Estou usando o loop while abaixo para ler um arquivo.

while read file
do
    FileFound="'find $DataDir -name $file -print 2>/dev/null'"
    if [ -n "$FileFound" ]; then
        echo $FileFound >> ${runDir}/st_$Region
        else
            echo $file >> ${APP_HOME}/${Region}_filesnotfound_$date.txt
        fi
done<${Region}_${date}.txt

Esse loop while lê um nome de arquivo e compara no datadir para descobrir se alguma correspondência está disponível. se disponível, colocará todo o caminho em um arquivo. se não estiver disponível, o coloca em um arquivo diferente. No entanto, esse script leva dois dias para ser concluído para a leitura de 8000 registros. Existe uma maneira de otimizá-lo?

    
por Archana S 10.01.2017 / 02:17

5 respostas

2

Se você estiver em um desktop Linux moderno, provavelmente terá uma ferramenta de indexação de arquivos como mlocate já instalou e indexou arquivos em segundo plano. Se sim, você pode usar isso:

while read file
do
    locate "$file" >> "${runDir}/st_$Region" || echo "$file" >> "${APP_HOME}/${Region}_filesnotfound_$date.txt"
done<"${Region}_${date}.txt"

Se os arquivos que você está procurando forem atualizados com freqüência, você poderá primeiro forçar manualmente o banco de dados a atualizar-se com updatedb ou o que for apropriado para sua versão de locate .

    
por 10.01.2017 / 19:47
1

com xargs + find

Uma solução é usar xargs para criar comandos insanamente longos em find que pesquisarão milhares de arquivos de uma só vez:

sed -e 's/^/-o -name /' "${Region}_${date}.txt" \
| xargs find "$DataDir" -false \
> "${runDir}/st_$Region"

O primeiro comando sed transforma cada nome de arquivo na expressão -o -name filename , que será anexada por xargs ao comando find . Então xargs executa o comando find que construiu. O resultado é armazenado diretamente no arquivo st_$Region .

Bem. Mas como vamos construir ${Region}_filesnotfound_$date.txt , a lista de arquivos que não foram encontrados? Apenas cruzando a lista original completa com a lista de arquivos encontrados:

comm -3 \
    <(sort -u "${Region}_${date}.txt") \
    <(xargs -L1 basename < "${runDir}/st_$Region" | sort -u) \
    > "${Region}_filesnotfound_$date.txt"

comm -3 suprime as linhas comuns entre os dois arquivos. Esses são pseudo-arquivos, na verdade. O segundo arquivo é o resultado do comando basename aplicado a cada arquivo encontrado. Ambos os arquivos são classificados.

com find + grep

Outra solução é grep dos nomes de arquivos da saída de find . grep oferece a possibilidade (através da opção -f ) de pesquisar uma série de padrões armazenados em um arquivo. Nós temos uma série de nomes de arquivos em um arquivo. Vamos criar uma lista de padrões e alimentá-la com grep :

find "$DataDir" \
| grep -f <(sed 's|.*|/&$|' "${Region}_${date}.txt") \
> "${runDir}/st_$Region"

O comando sed é obrigatório: ele ancora o nome do arquivo para pesquisar no final do caminho.

Quanto à lista de arquivos ausentes, ela seria construída da mesma maneira que a outra solução.

O problema com esta solução é que os nomes de arquivos podem conter caracteres que podem ser interpretados por grep : . , * , [ , etc. Nós teríamos que escapar deles com sed (deixo como um exercício para o leitor). É por isso que a primeira solução é ser a preferida da IMHO.

Por fim, observe que usei bash isms aqui (por exemplo, processar subestações <(...) ). Não espere que nenhuma das minhas soluções seja compatível com POSIX.

    
por 10.01.2017 / 04:03
1

Este script é para apenas 1 ocorrência de um arquivo específico. Portanto, se houver dois arquivos com o mesmo nome em diretórios diferentes, apenas um será relatado. Não foi testado.

declare -a arr
tmp1=$$tmp1

while read file
do
    base=$(basename "$file")
    echo "$base" >> "$tmp1"
    arr["$base"]="$file"
done <(find "$DataDir")

cat "$tmp1" | sort | uniq > "$tmp1"
tmp2=$$tmp2
cat "${Region}_${date}.txt" | sort | uniq > "$tmp2"

for file in "$(join <(cat "$tmp1") <(cat "$tmp2"))"
do
    echo "${arr["$file"]}" >> ${runDir}/st_$Region
done

for file in "$(cat "$tmp1" "$tmp2" | sort | uniq -u)"
do
    echo "$file" >> ${APP_HOME}/${Region}_filesnotfound_$date.txt
done

rm "$tmp1"
rm "$tmp2"
    
por 10.01.2017 / 03:21
0

Para cada iteração, você está rastreando toda a árvore de diretórios. Você deseja executar find apenas uma vez. Com ferramentas GNU:

find "$DataDir" -print0 |
  FOUND=${runDir}/st_$Region \
  NOTFOUND=${APP_HOME}/${Region}_filesnotfound_$date.txt \
  awk -F/ '
    ARGIND == 1 {files[$0]; notfound[$0]; next}
    $NF in files {print > ENVIRON["FOUND"]; unset notfound[$0]}
    END {
      for (f in notfound) print f > ENVIRON["NOTFOUND"]
    }'  "${Region}_${date}.txt" RS='
find "$DataDir" -print0 |
  FOUND=${runDir}/st_$Region \
  NOTFOUND=${APP_HOME}/${Region}_filesnotfound_$date.txt \
  awk -F/ '
    ARGIND == 1 {files[$0]; notfound[$0]; next}
    $NF in files {print > ENVIRON["FOUND"]; unset notfound[$0]}
    END {
      for (f in notfound) print f > ENVIRON["NOTFOUND"]
    }'  "${Region}_${date}.txt" RS='%pre%' -
' -
    
por 10.01.2017 / 18:44
-1

A parte lenta desse script é o find , que pesquisa todo o seu $DataDir para uma correspondência. Ao mover grande parte desse componente para fora do loop, você deve conseguir uma economia significativa de tempo:

ftmp=$(mktemp -t)
find "$DataDir" >"$ftmp" 2>/dev/null

while IFS= read -r file
do
    if grep -Fx -q "$file" "$ftmp"    # No RE patterns. Match full line
    then
        echo "$file" >>"$runDir/st_$Region"
    else
        echo "$file" >>"${APP_HOME}/${Region}_filesnotfound_$date.txt"
    fi
done <"${Region}_${date}.txt"

rm -f "$ftmp"

Se a sua lista de arquivos em ${Region}_${date}.txt for muito grande, você poderá economizar ainda mais passando o arquivo inteiro para grep e usando comm para identificar as entradas sem correspondência da lista completa e o conjunto de correspondências. A desvantagem aqui é que, como comm requer listas classificadas, as listas de resultados de saída também serão classificadas:

fdata=$(mktemp -t)
fmatch=$(mktemp -t)
find "$DataDir" >"$fdata" 2>/dev/null

# No RE patterns. Match full line
grep -Fx -f "${Region}_${date}.txt" "$fdata" |
    tee -a "$runDir/st_$Region" |
    sort >"$fmatch"

# Pick out the filenames that didn't match
sort "${Region}_${date}.txt" |
    comm -23 - "$fmatch" >>"${APP_HOME}/${Region}_filesnotfound_$date.txt"

rm -f "$fdata" "$fmatch"
    
por 10.01.2017 / 15:20