Прокручування через `ls 'призводить до скрипту bash shell


93

У когось є сценарій оболонки шаблону для того, щоб щось робити зі lsсписком імен каталогів та перебирати їх через кожне і щось робити?

Я планую зробити, ls -1d */щоб отримати список імен каталогів.

Відповіді:


109

Відредаговано, щоб не використовувати ls там, де робиться глобус, як @ shawn-j-goff та інші запропонували.

Просто використовуйте for..do..doneцикл:

for f in *; do
  echo "File -> $f"
done

Ви можете замінити *з *.txtбудь-якої іншої Glob , який повертає список (файлів, каталогів або що - небудь в цьому відношенні), команда , яка генерує список, наприклад, $(cat filelist.txt)або на самому ділі замінити його в списку.

У doциклі ви просто посилаєтесь на змінну циклу з префіксом знака долара (так $fу наведеному вище прикладі). Ви можете echoце зробити або зробити все, що завгодно.

Наприклад, перейменувати всі .xmlфайли в поточному каталозі на .txt:

for x in *.xml; do 
  t=$(echo $x | sed 's/\.xml$/.txt/'); 
  mv $x $t && echo "moved $x -> $t"
done

Або ще краще, якщо ви використовуєте Bash, ви можете використовувати розширення параметрів Bash, а не нерестування нижньої оболонки:

for x in *.xml; do 
  t=${x%.xml}.txt
  mv $x $t && echo "moved $x -> $t"
done

22
Що робити, якщо в імені файлу є пробіл?
Даніель А. Білий

4
На жаль, як сказав Даніель, код у цій відповіді порушиться, якщо будь-який з файлів чи папок містить пробіл чи новий рядок у їхньому імені. Це показує дуже поширене неправильне використання forциклів і типовий підводне спробу розбору результатів ls. @ DanielA.White, ви могли б розглянути unaccepting ця відповідь , якщо він не був корисним (або може ввести в оману), так як ви сказали, ви дієте на каталоги. Відповідь Шона Дж. Гоффа має забезпечити більш надійне та дієве рішення вашої проблеми.
slhck

4
-∞ Ви не повинні розбирати lsвихід , ви не повинні читати вихід у forциклі , ви повинні використовувати $()замість `` та використовувати більше котирувань ™ . Я впевнений, що @CoverosGene мав на увазі добре, але це просто жахливо.
l0b0

1
Кращою альтернативою, якщо ви дійсно хочете користуватися, lsє ls -1 | while read line; do stuff; done. Принаймні, той не відіб’ється на пробіли.
Еммануель Жубо

1
з mv -vтобою не потрібноecho "moved $x -> $t"
Дмитро Сандалов

68

Використання результатів lsдля отримання імен файлів - погана ідея . Це може призвести до несправності та навіть небезпечних сценаріїв. Це відбувається тому , що ім'я файлу може містити будь-які символи , крім /і nullхарактер, і lsне використовувати один з цих символів в якості роздільників, так що якщо ім'я файлу містить пробіл або символ нового рядка, ви будете отримувати несподівані результати.

Є два дуже хороших способи ітерації файлів. Тут я просто echoдемонстрував, що роблю щось із назвою файлу; ви можете використовувати все, що завгодно.

Перший полягає у використанні натільних функцій оболонки оболонки.

for dir in */; do
  echo "$dir"
done

Оболонка розширюється */на окремі аргументи, які forчитає цикл; навіть якщо у назві файлу є пробіл, новий рядок або будь-який інший дивний символ, forкожне повне ім’я буде бачитись як атомна одиниця; це ніяк не розбирає список.

Якщо ви хочете рекурсивно переходити до підкаталогів, це не буде робити, якщо ваша оболонка не має деяких розширених функцій глобалізації (наприклад, bashs globstar. Якщо ваша оболонка не має цих можливостей, або якщо ви хочете переконатися, що ваш сценарій буде працювати у різних системах, то наступним варіантом є використання find.

find . -type d -exec echo '{}' \;

Тут findкоманда викличе echoта передасть їй аргумент імені файлу. Це робиться один раз для кожного знайденого файлу. Як і в попередньому прикладі, тут немає розбору списку імен файлів; натомість ім'я файлу надсилається повністю як аргумент.

Синтаксис -execаргументу виглядає дещо смішним. findприймає перший аргумент після -execі трактує, що як програма, що запускається, так і кожен наступний аргумент, він береться як аргумент для переходу до цієї програми. Є два особливих аргументи, які -execпотрібно побачити. Перший - це {}; цей аргумент замінюється на ім'я файлу, яке findстворюються попередніми частинами . Другий - це означає ;, що findце кінець списку аргументів, які потрібно передавати програмі; findце потрібно, тому що ви можете продовжити більше аргументів, призначених findі не призначених для програми exec'd. Причиною цього \є те, що шкаралупу також лікують;спеціально - це являє собою кінець команди, тому нам потрібно уникнути цього, щоб оболонка давала це як аргумент, findа не споживає його для себе; Ще один спосіб отримати оболонку, щоб не ставитися до неї спеціально, - це помістити її в лапки: ';'працює так само добре, як \;для цієї мети.


2
+1 це безумовно шлях, коли потрібно створити список файлів і використовувати його в команді. find -exec обмежується лише можливістю запускати окремі команди. За допомогою циклу ви можете підключитися до вмісту серця.
MaQleod

+1. Я б хотів, щоб більше людей використовувало find. Є магія, яку можна зробити, і навіть сприйняті обмеження -execможна обійти. Цей -print0варіант також цінний для використання з xargs.
ghoti

Параметр циклу не відображатиме приховані каталоги. Варіант знаходження не відображатиме символічні посилання.
jinawee

15

Для файлів з пробілами у вас обов’язково потрібно переконатись у цитуванні змінної на зразок:

 for i in $(ls); do echo "$i"; done; 

або, ви можете змінити змінну середовища розділення вводу (IFS):

 IFS=$'\n';for file in $(ls); do echo $i; done

Нарешті, залежно від того, що ви робите, можливо, вам навіть не знадобиться ls:

 for i in *; do echo "$i"; done;

приємне використання підшару в першому прикладі
Jeremy L

Чому IFS потребує $ після призначення та перед новим символом?
Енді Ібанез

3
Назви файлів також можуть містити нові рядки. Пробиватись \nнедостатньо. Ніколи не рекомендується рекомендувати розбір результатів ls.
ghoti


4

Просто для додання відповіді CoverosGene, ось спосіб перерахувати лише імена каталогів:

for f in */; do
  echo "Directory -> $f"
done

1

Чому б не встановити IFS на новий рядок, а потім захопити висновок lsмасиву? Встановлення IFS у новий рядок повинно вирішувати проблеми із забавними символами у назвах файлів; використання lsможе бути приємним, оскільки у нього є вбудована система сортування.

(Під час тестування у мене виникли проблеми з налаштуванням IFS, \nале налаштування його на нову лінію працює, як це запропоновано в іншому місці):

Напр. (За умови, що бажаний lsшаблон пошуку передано $1):

SAVEIFS=$IFS
IFS=$(echo -en "\n\b")

FILES=($(/bin/ls "$1"))

for AFILE in ${FILES[@]}
do
    ... do something with a file ...
done

IFS=$SAVEIFS

Це особливо зручно в OS X, наприклад, для захоплення списку файлів, відсортованих за датою створення (найдавнішою до новітньої), lsкоманда є ls -t -U -r.


2
Імена файлів також можуть містити нові рядки, і вони часто роблять, коли користувачам дозволяється називати власні файли. Пробиватись \nнедостатньо. Єдині дійсні рішення використовують цикл for з розширенням імені шляху, або find.
ghoti

Єдиний надійний спосіб передати список імен файлів - це відокремити їх символом NUL, оскільки це єдиний, який точно не міститься у шляху до файлу.
glglgl

-3

Так я це роблю, але, мабуть, є більш ефективні способи.

ls > filelist.txt

while read filename; do echo filename: "$filename"; done < filelist.txt

6
Затримайте труби замість файлу:>ls | while read i; do echo filename: $i; done
Jeremy L

Класно. Слід сказати, що ви також можете використовувати $ EDITOR filelist.txt між двома командами. Багато речей, які можна зробити в редакторі, простіше, ніж у командному рядку. Однак це питання не стосується.
ТРЕЙ

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