Как перебирать файлы в каталоге с помощью Bash?

414

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

./MyProgram.exe Data/data1.txt [Logs/data1_Log.txt].

Вот псевдокод для того, что я хочу сделать:

for each filename in /Data do
  for int i = 0, i = 3, i++
    ./MyProgram.exe Data/filename.txt Logs/filename_Log{i}.txt
  end if
end for

Итак, я действительно озадачен тем, как создавать второй аргумент из первого, поэтому он выглядит как dataABCD_Log1.txt и запускает мою программу. Помощь очень ценится.

P.S. Я знаю, что есть аналогичные вопросы, но я ничего не нашел при создании имени моего файла журнала.

Теги:
for-loop
filenames

6 ответов

512
Лучший ответ

Несколько примечаний: когда вы используете Data/data1.txt в качестве аргумента, должно ли оно действительно быть /Data/data1.txt (с ведущей косой чертой)? Кроме того, должен ли внешний цикл проверять только файлы .txt или все файлы в /Data? Здесь ответ, предполагающий только файлы /Data/data1.txt и .txt:

#!/bin/bash
for filename in /Data/*.txt; do
    for ((i=0; i<=3; i++)); do
        ./MyProgram.exe "$filename" "Logs/$(basename "$filename" .txt)_Log$i.txt"
    done
done

Примечания:

  • /Data/*.txt расширяется до путей текстовых файлов в /Data (включая/Data/part)
  • $( ... ) запускает команду оболочки и вставляет ее вывод в этой точке в командной строке
  • basename somepath .txt выводит базовую часть somepath, с .txt удаленным с конца (например, /Data/file.txtfile)

Если вам нужно запустить MyProgram с Data/file.txt вместо /Data/file.txt, используйте "${filename#/}", чтобы удалить ведущую косую черту. С другой стороны, если это действительно Data не /Data, которое вы хотите отсканировать, просто используйте for filename in Data/*.txt.

  • 0
    basename -s не работает. Я смог запустить скрипт, изменив его на basename "$filename" .txt , теперь он работает по назначению. PS Я использую Cygwin, так что может быть дело.
  • 1
    Очевидно, basename -s - это нестандартное расширение - я отредактирую свой ответ, чтобы использовать стандартный синтаксис.
Показать ещё 5 комментариев
182

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

Например:

for filename in Data/*.txt; do
    [ -e "$filename" ] || continue
    # ... rest of the loop body
done

Ссылка: Bash Pitfalls

  • 107
    Переполнение стека означает, что вам никогда не придется извиняться за некромантию!
  • 5
    Это все еще своевременное предупреждение. Я думал, что создал свой сценарий неправильно, но у меня было расширение файла в нижнем регистре, а не в верхнем, он не нашел файлов и возвратил шаблон глобуса. тьфу.
Показать ещё 6 комментариев
67
for file in Data/*.txt
do
    for ((i = 0; i < 3; i++))
    do
        name=${file##*/}
        base=${name%.txt}
        ./MyProgram.exe "$file" Logs/"${base}_Log$i.txt"
    done
done

Подстановка name=${file##*/} (расширение параметра оболочки) удаляет ведущий путь до последнего /.

Подстановка base=${name%.txt} удаляет конечный .txt. Это немного сложнее, если расширения могут меняться.

  • 1
    Я считаю, что в вашем коде есть ошибка. Одна строка должна быть base=${name%.txt} вместо base=${base%.txt} .
  • 3
    @CaseyKlimkowsky: Да; когда код и комментарии не согласны, по крайней мере один из них является неправильным. В этом случае, я думаю, что это только один - код; часто, это на самом деле и то, и другое неправильно. Спасибо что подметил это; Я исправил это.
0

Вы можете использовать опцию output с нулевым разделением и read для перебора структур каталогов.

#!/bin/bash
find . -type f -print0 | while read -d $'\0' file; 
  do echo "$file" ;
done

Так что для вашего случая

#!/bin/bash
find . -maxdepth 1 -type f  -print0 | while read -d $'\0' file; do
  for ((i=0; i<=3; i++)); do
    ./MyProgram.exe "$file" 'Logs/'"'basename "$file"'""$i"'.txt'
  done
done
  • 1
    $'\0' - странный способ написания '' . Вы пропускаете IFS= и переключатель -r для read : ваш оператор чтения должен быть: IFS= read -rd '' file .
  • 0
    Я полагаю, что некоторые из них должны будут искать $'\0' и распределять некоторые точки стека. Собираюсь сделать правки, на которые вы указали. Каковы вредные последствия отсутствия IFS= попытки echo -e "ok \nok\0" | while read -d '' line; do echo -e "$line"; done там, кажется, не будет. Также -r я часто вижу значение по умолчанию, но не смог найти пример того, что он предотвращает.
Показать ещё 1 комментарий
0

Что-то я использую, чтобы избежать лишних строк или проверок:

#!/bin/bash
for filename in $(find /Data/*.txt 2> /dev/null); do
    for ((i=0; i<=3; i++)); do
        ./MyProgram.exe "$filename" "Logs/$(basename "$filename" .txt)_Log$i.txt"
    done
done

Идея запускает поиск в подоболочке и отправляет ошибки в /dev/null, так что вы никогда не зацикливаете на ошибке об отсутствующих файлах.

-2

Похоже, вы пытаетесь выполнить файл Windows (.exe) Конечно, вы должны использовать PowerShell. В любом случае, в оболочке Linux bash простой однострочной оболочки будет достаточно.

[/home/$] for filename in 'ls /Data/*.txt'; do for i in {0..3}; do ./MyProgam.exe Data/filename Logs/$filename_log$i.txt; done done

Или в баш

#!/bin/bash

for filename in 'ls /Data/*.txt'; 
   do
     for i in {0..3}; 
       do ./MyProgam.exe Data/filename.txt Logs/$filename_log$i.txt; 
     done 
 done

Ещё вопросы

Сообщество Overcoder
Наверх
Меню