Проблема со скриптом Bash

Я хочу написать простой скрипт для обнаружения файла, созданного вирусом Windows. Обычно он создает файл .exe с тем же именем, что и каталог, который он удаляет.

Вот сценарий. Это работает, только если имя пути не содержит \ n. Может кто-нибудь помочь мне исправить этот сценарий, пожалуйста!

#!/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
1
задан 26 March 2011 в 21:02

3 ответа

Это обработает имена файлов, содержащие любой символ, позволенный в имени файла, включая новые строки.

#!/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)

См. http://mywiki.wooledge.org/BashFAQ/020 и http://mywiki.wooledge.org/BashFAQ/100 для большего количества разработки.

2
ответ дан 26 March 2011 в 21:02

Вы можете использовать ( *.exe ) для решения этой проблемы в bash.

Помещение круглых скобок, подобных этим ( и ), вокруг шаблона означает, что он будет расширен и сохранен в массиве.

Затем вы можете перебирать каждый элемент массива, используя что-то вроде этого:

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

Вот полный пример:

$ 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'

Я могу предоставить полное решение только после того, как вы уточнить, как выглядит ваша структура каталогов, потому что я не уверен, что current_dir_name делает то, что вы думаете.

0
ответ дан 26 March 2011 в 21:02

У Bash есть специальная переменная, которая называется IFS.

он используется bash для того, чтобы узнать, как разбить строку на список файлов. (Внутренний разделитель полей)

По умолчанию он содержит пробел, символ табуляции и новую строку.

for f in `find ... разделится на символы, содержащиеся в переменной $ IFS. Если в именах файлов есть символ новой строки или пробел, возвращаемый командой find, он будет обрабатываться как другое имя файла и, следовательно, цикл будет выполняться для каждой части имени.

$ 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)

Мы могли бы настроить переменную $ IFS, но это сложно. Давайте посмотрим почему:

(Протестируйте этот код в сценариях, чтобы изменение переменной IFS не осталось в вашей оболочке, вы можете запутаться)

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

Как вы можете видеть Теперь bash не имеет никакого правила для разделения строки, возвращаемой командой find, и рассматривает весь вывод как одно значение.

Очевидно, что проблема здесь в том, что если мы используем for f in `find...., у нас нет никакого способа отличить новую строку, содержащуюся в файле, от новой строки, сгенерированной командой find.

Теперь для полноты картины я покажу вам, как заставить ваш подход работать, но, пожалуйста, посмотрите более простое решение в конце ответа.

Мы могли бы использовать другие символы-разделители и исправить вашу текущую проблему:

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

Здесь команда find -name "*.exe" -exec echo -n {}: \; возвращает список файлов, разделенных «:» (которые недопустимы в именах файлов Windows) ,

Теперь следующим шагом будет удаление этих файлов. Примите во внимание, что последний файл завершается символом «:», и, таким образом, bash выполняет итерацию в последний раз с пустой строкой.

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

Вы должны быть в состоянии выполнить также свой тест базового имени / имени каталога в этом цикле, как вы делали раньше.


Однако это решение безобразно и работает только потому, что у нас есть персонаж, на которого мы можем положиться.

Оказывается, что сама команда find может выполнять команды для каждого найденного файла.

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

Затем yourscript.sh получит полное имя в $ 1, и вы можете удалить его с помощью rm "$1", даже если он содержит символы новой строки и пробелы.

Команда find также может использоваться для выполнения встроенного теста базового имени / имени каталога, который вы выполняете в своем скрипте, но это более сложная тема.

0
ответ дан 26 March 2011 в 21:02

Другие вопросы по тегам:

Похожие вопросы: