Por que o laço for não executa no diretório?

3

No script a seguir, o primeiro loop para é executado conforme o esperado, mas não o segundo. Eu não recebo nenhum erro, parece que o script apenas trava.

HOME=/root/mydir

DIR=$HOME/var
DIRWORK=$HOME/Local

for f in $(find $DIR -type f); do

    lsof -n $f | grep [a-z] > /dev/null

    if [ $? != 0 ]; then
    echo "hi"       
    fi
done


for i in $(find $DIRWORK -type d -name work); do
    echo "2"
done
    
por Novice User 22.04.2013 / 07:17

2 respostas

5

Seu script é codificado de maneira perigosa.

Primeiro, suponho que você esteja usando o shell Bash desde que você o marcou '/ bash' e '/ for'.

Em minha resposta, citarei este ótimo Guia Bash , que é provavelmente a melhor fonte para aprender Bash por aí.

1) Nunca use uma Substituição de Comando , do tipo sem aspas. Há um grande problema aqui: usar uma expansão sem aspas para dividir a saída em argumentos.

Especificamente, esse $(find $DIRWORK -type d -name work) e $(find $DIR -type f) passarão por Divisão de palavras , portanto, se find encontrar um arquivo com espaços em seu nome, ou seja, "nome do arquivo", o resultado da divisão de palavras do Bash passará 2 argumentos para o comando for para iterar, ou seja, um para "arquivo" e um para "nome". Nesse caso, você espera ter um "arquivo: Nenhum arquivo ou diretório" e "nome: Nenhum arquivo ou diretório", em vez de potencialmente causar danos a eles, se eles realmente existirem.

2) Por convenção, variáveis de ambiente (PATH, EDITOR, SHELL, ...) e variáveis de shell internas (BASH_VERSION, RANDOM, ...) são totalmente capitalizadas. Todos os outros nomes de variáveis devem estar em minúsculas. Como os nomes das variáveis fazem distinção entre maiúsculas e minúsculas, essa convenção evita a sobreposição acidental de variáveis ambientais e internas.

Seu diretório $ DIRWORK quebra essa convenção, e ela também não é citada, portanto, se deixarmos que DIRWORK='/path/to/dir1 /path/to/dir2' , find procure em dois diretórios diferentes quando $ DIRWORK estiver sem aspas. O assunto de usar aspas é muito importante no Bash, então você deve "Dobrar aspas" a cada expansão, bem como qualquer coisa que possa conter um caractere especial, por exemplo "$ var", "$ @", "$ {array [@]}", "$ (comando)". Bash trata tudo dentro de 'aspas simples' como literal. Aprenda a diferença entre "e" e ". Veja Citações , Argumentos e você também pode querer dar uma olhada neste link: link

Esta é uma versão mais segura do seu script, que eu recomendo que você use em vez disso:

my_home="/root/mydir"

my_dir="$my_home/var"
dir_work="$my_home/Local"

while IFS= read -r -d '' f; do
    # I'm guessing that you also want to ignore stderr;
    # this is where the 2>&1 came from.
    if lsof -n "$f" | grep '[a-z]' > /dev/null 2>&1; then
        echo "hey, I'm safer now!"
    fi
done < <(find "$dir_work" -type f -print0)


while IFS= read -r -d '' f; do
    echo "2"
done < <(find "$dir_work" -type d -name 'work' -print0)

Como você pode ver, a variável IFS está configurada para ser vazia, evitando que read corte os espaços iniciais e finais de uma linha. O comando read usa uma string vazia ( -d '' ) como um delimitador para ler até atingir \ 0. find precisa ser modificado de acordo, portanto, usa a opção -print0 para delimitar seus dados com um \ 0 em vez de uma nova linha - que, de maneira surpreendente e maliciosa, pode fazer parte de um nome de arquivo. Dividir esse arquivo por \ n em duas partes quebrará nosso código.

Você pode querer ler sobre Substituição de processos se não entender meu script completamente.

A resposta anterior, que afirmou que find ... | while read name; do ...; done deve ser usada para ler a saída find s, também pode ser ruim. O loop while acima é executado em um novo subshell com sua própria cópia das variáveis copiadas do pai. Esta cópia é usada para o que você quiser. Quando o loop while for concluído, a cópia da sub-camada será descartada e as variáveis originais do pai não serão alteradas.

Se você pretende modificar algumas variáveis dentro desse while loop e usá-las posteriormente no pai, considere usar o script mais seguro acima, o que evitará a perda de dados.

    
por 22.04.2013 / 17:54
2

Este código

for i in $(find $DIRWORK -type d -name work); do
    echo "2"
done

primeiro executará esta linha

find $DIRWORK -type d -name work

espere até que find termine de executar e, em seguida, pegue a saída e coloque-a de volta no for loop

for i in the output of find; do
    echo "2"
done

somente então o loop for iniciará a execução.

Portanto, se o find estiver demorando para concluir o ciclo for , ele precisará aguardar muito antes de iniciar.

Tente cronometrar o comando find em um prompt interativo

$ time find $DIRWORK -type d -name work

e veja quanto tempo demora.

Observe também que você não deve usar um loop for para fazer um loop por nomes de arquivos. Use um loop while com read da seguinte forma:

find $DIRWORK -type d -name work | while read name; do
    echo "2"
done

Leia isto para mais informações.

Bônus: isso executa o loop while em paralelo a find . Isso significa que o loop while executará uma iteração assim que find imprimir uma linha. Ele não precisa esperar que find termine a execução.

    
por 22.04.2013 / 11:09

Tags