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


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

(Я використовую ехо та трубу для того, щоб писати багаторядкові команди.)

Коли я це роблю, він працює правильно на тих файлах, які не мають пробілів у їх іменах. Але інші ...

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

робить трюк! Щойно я новачок у баші, я хочу зробити все максимально простим - так нічого не намагаючись уникнути пробілів, не використовуючи xargsчи рішення read -r, хоча вони вирішують проблему.

Як деякі запитували: Так, використання цього замість stat *є дивним. Але я просто хотів знайти загальний спосіб застосувати ту саму команду на купі імен файлів у bash, використовуючи цикл for. Так statміг би виступати за gzip, gpgабо rm.


1
що не так stat *? (;-)
Рмано

Також дивіться mywiki.wooledge.org/ParsingLs
Rmano

Я просто використовую stat як приклад. :) Я хочу зібрати імена файлів з ls, а потім використовувати результати ls у циклі bash.
користувач258532

Відповіді:


12

Багаторазова цитата echo 'ускладнює річ.

Ви можете просто використовувати:

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

Але також

stat -- * 

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

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

... і якщо ви також хочете приховані файли, просто використовуйте * .*як зразок, але тоді пам’ятайте про це .і ..будете в наборі .

Як сторону, ви не повинні аналізувати lsвихід .


(1) але якщо у вас є імена файлів з новими рядками, ви дещо заслуговуєте на це ... ;-)


", а потім застосувати команду (навіщо?)" -> stat просто служить довільною командою, оскільки я намагаюся робити петлі bash з іменами файлів. Це може бути gpg, gzip або що-небудь ще.
користувач258532

@ user258532 незалежно від команди, завжди використовуйте for f in *; do command "$f"; done. Ніколи не розбирайте ls, звичайно ніколи не робіть це в for циклі і навіщо використовувати echo?
тердон

echo: Тому що мені подобається писати команди в декілька рядків ... :)
user258532

2
@ user258532 А? Навіщо тобі для цього потрібне відлуння? Просто натисніть клавішу Enter і продовжте новий рядок. Якщо кінець рядка з відкритою цитатою чи на одному з do, |, і &&т.д., ви можете продовжити на новій лінії. Або це, або використовуйте гередоки. Немає підстав для використання, echoі це також може спричинити проблеми.
тердон

"Просто натисніть клавішу Enter та продовжте новий рядок." Ой. :-D Тепер мій IQ офіційно нижче 0 - ви знаєте, я справді новачок у башті та подібні речі. Але я фактично заробляю гроші на R (статистичний набір і сценарій мови).
користувач258532

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 . Є кращі і надійніші способи. Ви також можете прочитати це: Чому * не * розбір `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 {}

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 () повертає значення котируваного символу. Щось між цитатами - це дійсне ім'я файлу з пробілами.


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

1

Я зіткнувся з іншими екземплярами проблем пробілу для циклів, тому наступна команда (imo більш надійна) - це те, що я зазвичай використовую. Він також добре вписується в труби.

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

Ви можете комбінувати це grepабо використовувати findнатомість:

$ find ./ -maxdepth 2 | grep '^\./[/a-z]+$' | while read line; do stat "$line"; done;

Ваша перша відповідь не відповідає назви файлів, які містять звороту косу рису, або провідні чи кінцеві пробіли. Ваша друга відповідь не відповідає повністю, якщо ви не додасте -Eпараметр grep, без якого він не розпізнається +в регулярному виразі. Навіть тоді це питання є хитним і невдалим у цьому питанні, оскільки ви grep видаляєте назви файлів, що містять пробіли . Він також видаляє назви файлів, що містять цифри (цифри) та розділові знаки (наприклад, дужки), як це роблять приклади у питанні. І це навіть не згадує імена файлів, які містять новий рядок або починаються з -(тире).
Скотт

-1

Натомість ви можете перейменовувати свої файли, замінюючи пробіл, іншим символом, наприклад, підкресленням, так що ви позбавитесь від цієї проблеми:

Для цього легко запустіть команду:

for file in * ; do mv "$f" "${f// /_}" ; done

-2

Ця відповідь вирішить проблему розбору lsта піклується про відстані та нові рядки

Спробуйте це, це вирішить вашу проблему за допомогою внутрішнього сепаратора IFS.

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

Але також ви можете її легко вирішити без необхідності розбору результатів використання

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

1
не працює для прихованих файлів.
AB

2
ОП не запитували приховані файли
Maythux

1
Я не бачу, чому вам тут потрібно змінювати IFS: цитування змінної повинно бути достатньо, щоб запобігти поділу слова, безумовно?
steeldriver

для розбору ls .
Maythux

Бо що таке низовий !!!
Maythux
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.