Um problema com o script Bash

1

Eu quero escrever um script simples para detectar um arquivo criado pelo vírus do Windows. Geralmente cria um arquivo .exe, com o mesmo nome, como o diretório que ele desce.

Aqui está o script. Só funciona se o nome do caminho não contiver \ n. Alguém pode me ajudar a corrigir este script, por favor!

#!/bin/bash
if [ $# == 0 ]; then
    echo ""
    echo "==== Give me a directory to begin with! ===="
    echo ""
    exit
fi

for f in 'find $1 -name '*.exe'' ; do
    filename='basename "$f" .exe'
    dir_name='dirname "$f"'
    current_dir_name='basename "$dir_name"'

    if [ $filename == $current_dir_name ]; then
        rm -f "$f"  # It can't remove files 
                    # where the path contains spaces or \n ??!!
    fi
done
    
por c.sokun 01.11.2010 / 11:37

3 respostas

5

Bash tem uma variável especial chamada IFS.

é usado pelo bash para saber como dividir uma string em uma lista de arquivos. (Separador de campo interno)

Por padrão, ele contém espaço, tabulação e nova linha.

for f in 'find ... será dividido nos caracteres contidos na variável $ IFS. Se houver uma nova linha ou espaço nos nomes de arquivo retornados pelo comando find, ele será tratado como um nome de arquivo diferente e, portanto, o loop será executado para cada parte do nome.

$ ls -l
-rw-r--r-- 1 marko marko 0 2010-11-01 12:06 my?bad.exe
-rw-r--r-- 1 marko marko 0 2010-11-01 12:06 space bad.exe
$ for f in $(find -name "*.exe"); do echo "ITERATING ($f)"; done 
ITERATING (./space)
ITERATING (bad.exe)
ITERATING (./my)
ITERATING (bad.exe)

Podemos ajustar a variável $ IFS, mas é complicado. Vamos ver porque:

(Teste este código em scripts para que a mudança da variável IFS não permaneça no seu shell, você pode ficar confuso)

$ IFS=""; for f in $(find -name "*.exe"); do echo "ITERATING ($f)"; done
ITERATING (./space bad.exe
./my
bad.exe)

Como você pode ver, agora o bash não tem nenhuma regra para dividir a string retornada pelo comando find e trata toda a saída como um único valor.

Claramente, o problema aqui é que, se usarmos for f in 'find.... , não temos como distinguir uma nova linha contida no arquivo de uma nova linha gerada pelo comando find.

Agora, por questões de integridade, mostrarei a você como fazer sua abordagem funcionar, mas, por favor, veja uma solução mais fácil no final da resposta.

Poderíamos usar outros caracteres delimitadores e corrigir seu problema atual:

$ for f in $(find -name "*.exe" -exec echo -n {}: \;); do echo "ITERATING ($f)"; done;
ITERATING (./space bad.exe)
ITERATING (./my
bad.exe)
ITERATING ()

Aqui, o comando find -name "*.exe" -exec echo -n {}: \; retorna a lista de arquivos separados por ":" (que não são válidos nos nomes de arquivos do Windows).

Agora, o próximo passo seria realmente excluir esses arquivos. Leve em consideração que o último arquivo é terminado por ":" e, portanto, o bash itera uma última vez com uma string vazia.

IFS=":"; for f in $(find -name "*.exe" -exec echo -n {}: \;); do if [ ! -z "$f" ]; then rm "$f"; fi; done;

Você deve ser capaz de realizar também o teste basename / dirname nesse loop, como fez antes.

No entanto, esta solução é feia e funciona apenas porque temos um personagem em que podemos confiar.

Acontece que o próprio comando find pode executar comandos para cada arquivo que encontrar.

find -name "*.exe" -exec ./yourscript.sh {} \;

Em seguida, o yourscript.sh receberá o nome completo em $ 1 e você poderá excluir com rm "$1" , mesmo que contenha novas linhas e espaços.

O comando find também pode ser usado para executar inline o teste basename / dirname que você faz dentro do seu script, mas é um tópico mais avançado.

    
por ithkuil 01.11.2010 / 12:30
2

Você pode usar ( *.exe ) para resolver esse problema em bash .

Colocar parênteses como este ( e ) em torno de um padrão significa que ele será expandido e armazenado em uma matriz.

Depois, você pode iterar sobre cada item da matriz usando algo parecido com isto:

exes=( *.exe )
for exe in "${exes[@]}"; do
    echo "'$exe'"
done

Veja um exemplo completo:

$ mkdir 'dir
> with
> spaces'
$ touch 'dir
> with
> spaces/dir
> with
> spaces'
$ touch 'dir
> with
> spaces/other
> file'
$ find .
.
./dir?with?spaces
./dir?with?spaces/dir?with?spaces
./dir?with?spaces/other?file
$ cd 'dir
> with
> spaces'
$ files=( * )
$ for file in "${files[@]}"; do
> echo "'$file'"
> done
'dir
with
spaces'
'other
file'

Eu só posso fornecer uma solução completa depois de esclarecer como é sua estrutura de diretórios, porque não tenho certeza se current_dir_name está fazendo o que você acha que é.

    
por Mikel 03.02.2011 / 05:03
2

Isso manipulará nomes de arquivos contendo qualquer caractere permitido em um nome de arquivo, incluindo novas linhas.

#!/bin/bash

if (($# != 1)); then
     echo >&2 "Usage: $0 directory"
     exit 1
fi

while IFS= read -r -d '' file; do 
    base=${file##*/} dir=${file%/*/*}
    if [[ $dir/${base%.exe}/$base -ef $file ]]; then
        echo rm "$file"
    fi
done < <(find "$1" -type f -name "*.exe" -print0)

Veja o link e link para mais elaboração.

    
por geirha 03.02.2011 / 03:54