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

8

Я хочу сделать что-то несколько раз в списке файлов. У файлов в вопросах есть пробелы в их именах:

david@david: ls -l
total 32
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha
-rw-rw-r-- 1 david david  0 Mai  8 11:55 haha~
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (3rd copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (4th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (5th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (6th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (7th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (another copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (copy)

Теперь я хочу оценить каждый из этих файлов:

david@david: echo '
for file in $(ls)
do
stat $file
done' | bash

(Я использую echo и pipe для написания многострочных команд.)

Когда я это делаю, он корректно работает с теми файлами, в именах которых нет пробелов. Но остальные ...

stat: cannot stat ‘(another’: No such file or directory
stat: cannot stat ‘copy)’: No such file or directory

Изменение $(ls)к "$(ls)"или $fileк "$file"не работает. Что я могу сделать?

Редактировать:

echo '
for files in *
do
stat "$files"
done' | bash

делает трюк! Поскольку я новичок в bash, я хочу, чтобы все было как можно проще - не пытаясь избежать пробелов, использовать xargsили read -rрешить их, хотя они решают проблему.

Как некоторые спрашивают: да, использовать это вместо stat *странно. Но я просто хотел найти общий способ применения одной и той же команды к группе имен файлов в bash, используя цикл for. Так statможет стоять gzip, gpgили rm.

user258532
источник
1
что не так с stat *? (;-)
Rmano
Также см. Mywiki.wooledge.org/ParsingLs
Rmano
Я просто использую stat в качестве примера. :) Я хочу собрать имена файлов с помощью ls, а затем использовать результаты ls в цикле bash.
user258532

Ответы:

12

Многократная цитата из echo 'усложняет вещь.

Вы можете просто использовать:

for f in *; do stat -- "$f"; done

Но и

stat -- * 

... и если вы хотите собрать файлы и затем применить команду (почему?), вы можете пойти (но будьте осторожны с файлом, содержащим новые строки ... (1))

for f in *; do echo "$f"; done | xargs stat --

... и если вы хотите скрытые файлы, просто используйте * .*в качестве шаблона, но затем помните, что .и ..будет в наборе .

Кроме того, вы не должны анализировать lsвывод .


(1) но если у вас есть имена файлов с символами новой строки, вы это заслужили ... ;-)

Rmano
источник
«а затем примените команду (почему?)» -> stat просто служит произвольной командой, так как я пытаюсь сделать циклы bash с именами файлов. Это может быть gpg, gzip или что-то еще.
user258532
@ user258532 независимо от команды, всегда используйте for f in *; do command "$f"; done. Никогда не разбирайте ls, конечно, никогда не делайте это в for цикле и зачем использовать echo?
Тердон
echo: потому что мне нравится писать команды в несколько строк ... :)
user258532
2
@ user258532 А? Зачем вам эхо для этого? Просто нажмите Enter и продолжайте перевод строки. Если конец строки с открытой цитатой или на одном из do, |, и &&т.д., вы можете продолжить на новой линии. Либо так, либо используйте heredocs. Нет причин для использования, echoи это также может вызвать проблемы.
Тердон
«Просто нажмите Enter и продолжайте перевод». Уч. :-D Теперь мой IQ официально ниже 0 - вы знаете, я действительно новичок в bash и тому подобное. Но на самом деле я зарабатываю свои деньги с помощью R (статистический набор и язык сценариев).
user258532
6

На заметку: вы можете разделить длинные / сложные команды на несколько строк, добавив пробел с обратной косой чертой и нажимая Enterкаждый раз, когда вы хотите начать запись в новую строку, вместо того, чтобы разбивать несколько процессов с помощью echo [...] | bash; также вы должны заключить $fileв двойные кавычки, чтобы предотвратить statразрыв в случае имен файлов, содержащих пробелы:

for file in $(ls); \
do \
stat "$file"; \
done

Проблема в том, что $(ls)расширяется список имен файлов, содержащих пробелы, и то же самое произойдет и с "$(ls)".

Даже решая эту проблему, этот метод будет разбивать имена файлов, содержащие обратную косую черту, и имена файлов, содержащие символы новой строки (как указал тердон).

Решение для обеих задач было бы трубы выход findк whileпетле работает read -rтак , что на каждой итерации, read -rбудет хранить одну строку find«с выходом в $file:

find . -maxdepth 1 -type f | while read -r file; do \
    stat "$file"; \
done
кос
источник
1
а скрытые файлы? :)
AB
5
Это все равно не сработает на именах файлов, содержащих переводы строк. Только не разбирайся ls. Когда-либо.
Тердон
4
@ user258532 нет, это не так. Серьезно, только не разбирайсяls . Есть лучшие и более надежные способы. Вы также можете прочитать это: почему * not * parse `ls`? Больше подробностей.
Тердон
1
Проблема здесь не ls, это for- forперебирает (МФС отделенный) слова даны после inkeyword`
Glenn Джекман
1
@glennjackman ну да, это комбинация forи ls. for f in *было бы хорошо, например.
Тердон
3

Используйте старый добрый find, работает со скрытыми файлами, переводами строки и пробелами.

find . -print0 | xargs -I {} -0 stat {}

или любой другой вместо stat

find . -print0 | xargs -I {} -0 file {}
find . -print0 | xargs -I {} -0 cat {}
AB
источник
1

Как парень из R, я уже нашел обходной путь в R:

filenames <- dir(); # reads file names into an array.
                    # It works also recursively
                    # with dir(recursive=TRUE)
for (i in 1:length(filenames)) {
system(     # calls a system function
 paste(     # join stat and the file name
  "stat",
  filenames[i]
 )
)
}

Я знаю, это безумие. Хотелось бы, чтобы выходные данные lsбыло легче проанализировать ... R может иметь дело с пробелами, потому что dir () возвращает значение в кавычках. Все, что находится в кавычках, является допустимым именем файла с пробелами.

user258532
источник
3
Не беспокойтесь (но +1 за усилие! :). Просто используйте for f in *, нажмите Enter и продолжите новую строку: doнажмите снова stat "$f"Enter, снова doneвведите Enter. Это команда, разделенная на 4 строки, которая не разбивается ни на какие имена файлов.
Тердон
1

Я сталкивался с другими случаями проблем с пробелами в циклах for, поэтому я обычно использую следующую (более надежную) команду. Это также хорошо вписывается в трубы.

$ ls | while read line; do stat "$line"; done;

Вы можете комбинировать это с grepили использовать findвместо:

$ find ./ -maxdepth 2 | grep '^\./[/a-z]+$' | while read line; do stat "$line"; done;
Tyzoid
источник
Ваш первый ответ терпит неудачу на именах файлов, которые содержат обратную косую черту или начальные или конечные пробелы. Ваш второй ответ полностью потерпит неудачу, если вы не добавите -Eпараметр grep, без которого он не будет распознаваться +в регулярном выражении. Даже тогда, это вопрос, который вы пропустили, поскольку вы grep удаляете имена файлов, содержащие пробелы . Он также удаляет имена файлов, содержащие цифры (цифры) и знаки препинания (например, круглые скобки), как это делают примеры в вопросе. И это даже не упоминает имена файлов, которые содержат новую строку или начинаются с -(тире).
Скотт
-1

Этот ответ решит проблему разбора lsи позаботится о пробелах и новых строках

Попробуйте, это решит вашу проблему с помощью внутреннего разделителя полей IFS.

IFS="\n" for f in $(ls); do   stat "$f"; done

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

for f in *; do   stat "$f"; done
Maythux
источник
1
не работает для скрытых файлов.
AB
2
ОП не просил скрытых файлов
Maythux
1
Я не понимаю, почему вам нужно изменить IFSздесь: кавычки переменной должно быть достаточно, чтобы предотвратить разделение слов, не так ли?
сталь-водитель
для разбора ls .
Maythux
За что понизить !!!
Maythux
-1

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

Для этого легко запустите команду:

for file in * ; do mv "$f" "${f// /_}" ; done
Maythux
источник