Как зациклить файлы в каталоге и изменить путь и добавить суффикс к имени файла

564

Мне нужно написать скрипт, который запускает мою программу с другими аргументами, но я новичок в 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 for
end for

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

Dobrobobr
источник

Ответы:

742

Сначала пара замечаний: когда вы используете 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.txt-> file)

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

Гордон Дэвиссон
источник
1
Очевидно basename -s, это нестандартное расширение - я отредактирую свой ответ, чтобы использовать стандартный синтаксис.
Гордон Дэвиссон
21
Если файлы не найдены / соответствуют шаблону, я обнаружил, что блок выполнения циклов по-прежнему вводится один раз с именем файла = "/Data/*.txt". Как я могу избежать этого?
Оливер Пирмейн
20
@OliverPearmain Либо используйте shopt -s nullglobперед циклом (и shopt -u nullglobпосле, чтобы избежать проблем в дальнейшем), либо добавьте if [[ ! -e "$filename ]]; then continue; fiв начале цикла, чтобы пропустить несуществующие файлы.
Гордон Дэвиссон
9
Это не работает, когда есть файлы, в имени которых есть пробелы.
Иса Хассен
4
@Isa Должен работать с пробелами, если все двойные кавычки на месте. Не используйте двойные кавычки, и у вас будут проблемы с пробелами.
Гордон Дэвиссон
345

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

Например:

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

Ссылка: Bash Pitfalls

Конг ма
источник
8
Это все еще своевременное предупреждение. Я думал, что создал свой скрипт неправильно, но у меня было расширение файла в нижнем регистре, а не в верхнем, он не нашел файлов и возвратил шаблон глобуса. тьфу.
RufusVS
5
Так как используется тег Bash: вот для чего shopt nullglob! (или shopt failglobможет использоваться тоже, в зависимости от желаемого поведения).
gniourf_gniourf
Кроме того, это также проверяет файлы, удаленные во время обработки, до того, как цикл достигнет их.
loxaxs
1
Также обрабатывает случай, когда у вас есть каталог с именемdir.txt
vidstige
75
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. Немного сложнее, если расширения могут отличаться.

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

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

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

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

#!/bin/bash
find . -maxdepth 1 -type f  -print0 | while IFS= read -r -d $'\0' file; do
  for ((i=0; i<=3; i++)); do
    ./MyProgram.exe "$file" 'Logs/'"`basename "$file"`""$i"'.txt'
  done
done

дополнительно

#!/bin/bash
while IFS= read -r -d $'\0' file; do
  for ((i=0; i<=3; i++)); do
    ./MyProgram.exe "$file" 'Logs/'"`basename "$file"`""$i"'.txt'
  done
done < <(find . -maxdepth 1 -type f  -print0)

запустит цикл while в текущей области действия скрипта (процесса) и позволит при необходимости использовать вывод find для установки переменных

Джеймс
источник
2
$'\0'это странный способ написания ''. Вы упускаете IFS=и -rпереключатель для read: вашего чтения заявления должен быть: IFS= read -rd '' file.
gniourf_gniourf
Я полагаю, что некоторые из них нуждаются в поиске $'\0'и распределении точек стека. Собираюсь сделать правки, на которые вы указали. Каковы вредные последствия отсутствия IFS=попыток, echo -e "ok \nok\0" | while read -d '' line; do echo -e "$line"; doneкажется, не будет. Также -r я часто вижу по умолчанию, но не смог найти пример того, что он предотвращает.
Джеймс
4
IFS=требуется, если имя файла заканчивается пробелом: try is with touch 'Prospero '(обратите внимание на завершающий пробел). Также вам нужен -rпереключатель в случае, если имя файла имеет обратную косую черту: попробуйте touch 'Prospero\n'.
gniourf_gniourf
Недооцененный ответ. Попробуйте самые популярные в каталоге, содержащем более 100 тыс. Файлов. Удачи, если вы находитесь на бюджетной машине.
конфетти
-1

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

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

Или в баш

#!/bin/bash

for filename in /Data/*.txt; 
   do
     for i in {0..3}; 
       do ./MyProgam.exe Data/filename.txt Logs/$filename_log$i.txt; 
     done 
 done
user3183111
источник