Подтвердить что ты не робот

Bash и имена файлов с пробелами

Ниже приведена простая командная строка Bash:

grep -li 'regex' "filename with spaces" "filename"

Нет проблем. Также следующие работы прекрасно:

grep -li 'regex' $(<listOfFiles.txt)

где listOfFiles.txt содержит список имен файлов, которые будут grepped, один имя файла в строке.

Проблема возникает, когда listOfFiles.txt содержит имена файлов с встроенные пространства. Во всех случаях, которые я пробовал (см. Ниже), Bash разделяет имена файлов в пространствах, поэтому, например, строка в listOfFiles.txt содержащее имя типа ./this is a file.xml, пытается запустить grep на каждой части (./this, is, a и file.xml).

Я думал, что я довольно продвинутый пользователь Bash, но я не могу найти простое магическое заклинание, чтобы заставить это работать. Вот что я имею попробовал.

grep -li 'regex' `cat listOfFiles.txt`

Не работает, как описано выше (я действительно не ожидал, что это сработает), поэтому я подумал, что я бы поставил кавычки вокруг каждого имени файла:

grep -li 'regex' `sed -e 's/.*/"&"/' listOfFiles.txt`

Bash интерпретирует кавычки как часть имени файла и дает "Нет таких файла или каталога "для каждого файла (и все еще разделяет имена файлов с заготовки)

for i in $(<listOfFiles.txt); do grep -li 'regex' "$i"; done

Это не так, как для первоначальной попытки (т.е. ведет себя так, как будто кавычки игнорируются) и очень медленно, так как он должен запускать один 'grep' процесс на файл вместо обработки всех файлов в одном вызове.

Следующие работы, но требуют некоторого осторожного двойного экранирования, если регулярное выражение содержит метасимволы оболочки:

eval grep -li 'regex' `sed -e 's/.*/"&"/' listOfFiles.txt`

Это единственный способ построить командную строку, чтобы она правильно обрабатывать имена файлов с пробелами?

4b9b3361

Ответ 1

Попробуйте следующее:

(IFS=$'\n'; grep -li 'regex' $(<listOfFiles.txt))

IFS является внутренним полевым разделителем. Установив его в $'\n', команда Bash использует символ новой строки для разграничения имен файлов. Его значение по умолчанию $' \t\n' и может быть напечатано с помощью cat -etv <<<"$IFS".

Включение script в скобки начинается подоболочка, так что только команды в скобках зависят от пользовательского значения IFS.

Ответ 2

cat listOfFiles.txt |tr '\n' '\0' |xargs -0 grep -li 'regex'

Параметр -0 в xargs указывает xargs использовать нулевой символ, а не пробел, как терминатор имен файлов. Команда tr преобразует входящие строки новой строки в нулевой символ.

Это соответствует требованию OP, чтобы grep не вызывалось несколько раз. По моему опыту, для большого количества файлов, избегающих множественных вызовов grep, значительно улучшается производительность.

Эта схема также исключает ошибку в исходном методе OP, потому что его схема будет ломаться, где listOfFiles.txt содержит несколько файлов, которые превышают размер буфера для команд. xargs знает о максимальном размере команды и будет вызывать grep несколько раз, чтобы избежать этой проблемы.

Связанная с использованием xargs и grep проблема заключается в том, что grep будет префикс вывода с именем файла при вызове с несколькими файлами. Поскольку xargs вызывает grep с несколькими файлами, вы получите вывод с префиксом имени файла, но не для одного файла в listOfFiles.txt или в случае нескольких вызовов, где последний вызов содержит одно имя файла. Чтобы добиться согласованного вывода add/dev/null в команду grep:

cat listOfFiles.txt |tr '\n' '\0' |xargs -0 grep -i 'regex' /dev/null

Обратите внимание, что это не было проблемой для OP, потому что он использовал параметр -l для grep; однако это, вероятно, будет проблемой для других.

Ответ 3

Это работает:

while read file; do grep -li dtw "$file"; done < listOfFiles.txt

Ответ 4

Хотя это может привести к перегрузке, это мое любимое решение:

grep -i 'regex' $(cat listOfFiles.txt | sed -e "s/ /?/g")

Ответ 5

Обратите внимание, что если вы каким-то образом закончили список в файле с окончанием строки Windows, \r\n, NONE из примечаний выше о разделителе входных файлов $IFS (и цитировании аргумента) будет работать; поэтому убедитесь, что окончание строк правильно \n (я использую scite для отображения окончаний строки и легко изменяю их от одного к другому).

Также работает cat, подключенный к while file read ... (видимо, без необходимости устанавливать разделители):

cat <(echo -e "AA AA\nBB BB") | while read file; do echo $file; done

... хотя для меня это было более актуально для "grep" через каталог с пробелами в именах файлов:

grep -rlI 'search' "My Dir"/ | while read file; do echo $file; grep 'search\|else' "$ix"; done

Ответ 6

С помощью Bash 4 вы также можете использовать встроенную функцию mapfile для установки массива, содержащего каждую строку, и итерации по этому массиву:

$ tree
.
├── a
│   ├── a 1
│   └── a 2
├── b
│   ├── b 1
│   └── b 2
└── c
    ├── c 1
    └── c 2

3 directories, 6 files
$ mapfile -t files < <(find -type f)
$ for file in "${files[@]}"; do
> echo "file: $file"
> done
file: ./a/a 2
file: ./a/a 1
file: ./b/b 2
file: ./b/b 1
file: ./c/c 2
file: ./c/c 1