Як перекинути файли в каталозі та змінити шлях та додати суфікс до імені файлу


562

Мені потрібно написати сценарій, який запускає мою програму з різних аргументів, але я новачок у 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 і запустив свою програму.

Відповіді:


736

По-перше, кілька зауважень: коли ви використовуєте Data/data1.txtв якості аргументу, чи справді це повинно бути /Data/data1.txt(з провідним косою рисою)? Також, чи повинен зовнішній цикл сканувати лише файли .txt або всі файли в / Дані? Ось відповідь, припускаючи /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розширюється до шляхів текстових файлів у / Дані ( включаючи / Дані / частина)
  • $( ... ) запускає команду shell і вставляє її висновок у цей момент у командному рядку
  • basename somepath .txtвиводить базову частину шляху, при цьому .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 Це має працювати з пробілами, доки всі подвійні лапки є на місці. Залиште будь-яку з подвійних лапок, і у вас виникнуть проблеми з пробілом.
Гордон Девіссон

344

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

Наприклад:

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

Довідка: Підводні камені Баша


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: Так; коли код і коментарі не згодні, принаймні один з них є неправильним. У цьому випадку я думаю, що це лише один - код; часто, насправді обидва помиляються. Дякую, що вказали на це; Я це виправив.
Джонатан Леффлер

8

Ви можете використовувати параметр висновку "null відокремлений", щоб безпечно читати ітерацію структур каталогів.

#!/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 у поточному обсязі сценарію (процесу) та дозволить, якщо потрібно, вихід знаходження використовувати при встановленні змінних


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

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