Cara mengulang file di direktori dan mengubah jalur dan menambahkan akhiran ke nama file

563

Saya perlu menulis skrip yang memulai program saya dengan argumen yang berbeda, tetapi saya baru di Bash. Saya memulai program saya dengan:

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

Inilah pseudocode untuk apa yang ingin saya lakukan:

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

Jadi saya benar-benar bingung bagaimana cara membuat argumen kedua dari argumen pertama, jadi sepertinya dataABCD_Log1.txt dan mulai program saya.

Dobrobobr
sumber

Jawaban:

739

Beberapa catatan pertama: ketika Anda menggunakan Data/data1.txtsebagai argumen, haruskah itu benar-benar /Data/data1.txt(dengan garis miring)? Juga, haruskah loop luar hanya memindai file .txt, atau semua file di / Data? Inilah jawaban, dengan asumsi /Data/data1.txtdan hanya file .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

Catatan:

  • /Data/*.txtmemperluas ke jalur file teks di / Data ( termasuk bagian / Data /)
  • $( ... ) menjalankan perintah shell dan memasukkan hasilnya pada titik itu di baris perintah
  • basename somepath .txtmenampilkan bagian dasar somepath, dengan .txt dihapus dari ujung (mis /Data/file.txt- - file)

Jika Anda perlu menjalankan MyProgram, Data/file.txtalih - alih /Data/file.txtgunakan "${filename#/}"untuk menghapus garis miring utama. Di sisi lain, jika Anda benar-benar Datatidak /Dataingin memindai, gunakan saja for filename in Data/*.txt.

Gordon Davisson
sumber
1
Rupanya basename -sekstensi tidak standar - saya akan mengedit jawaban saya untuk menggunakan sintaks standar.
Gordon Davisson
21
Jika tidak ada file yang ditemukan / cocok dengan wildcard saya menemukan blok untuk menjalankan loop masih dimasukkan sekali dengan nama file = "/ Data/*.txt". Bagaimana saya bisa menghindari ini?
Oliver Pearmain
20
@OliverPearmain Baik gunakan shopt -s nullglobsebelum loop (dan shopt -u nullglobsetelah untuk menghindari masalah nanti), atau tambahkan if [[ ! -e "$filename ]]; then continue; fipada awal loop, sehingga akan melewati file yang tidak ada.
Gordon Davisson
9
Ini tidak berfungsi ketika ada file yang berisi spasi putih di namanya.
Isa Hassen
4
@Isa Itu harus bekerja dengan spasi putih, selama semua tanda kutip ganda sudah ada. Biarkan salah satu dari tanda kutip ganda keluar, dan Anda akan memiliki masalah dengan spasi putih.
Gordon Davisson
345

Maaf untuk membatalkan utas, tetapi setiap kali Anda mengulangi file dengan globbing, ada baiknya untuk menghindari kasus sudut di mana glob tidak cocok (yang membuat variabel loop meluas ke string pola glob (tidak cocok) dengan string itu sendiri).

Sebagai contoh:

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

Referensi: Bash Pitfalls

Cong Ma
sumber
8
Ini masih peringatan tepat waktu. Saya pikir saya telah membuat skrip saya salah, tetapi ekstensi huruf saya lebih kecil daripada huruf besar, tidak menemukan file, dan mengembalikan pola glob. ya
RufusVS
5
Karena tag Bash digunakan: itulah gunanya shopt nullglob! (atau shopt failglobdapat digunakan juga, tergantung pada perilaku yang Anda inginkan).
gniourf_gniourf
Selain itu, ini juga memeriksa file yang dihapus selama pemrosesan, sebelum loop mencapai mereka.
loxaxs
1
Juga menangani kasus di mana Anda memiliki direktori bernamadir.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

The name=${file##*/}substitusi ( ekspansi parameter shell ) menghapus terkemuka path hingga terakhir /.

The base=${name%.txt}substitusi menghilangkan trailing .txt. Agak sulit jika ekstensi dapat bervariasi.

Jonathan Leffler
sumber
3
Saya percaya ada kesalahan dalam kode Anda. Seharusnya satu baris base=${name%.txt}, bukan base=${base%.txt}.
caseklim
4
@CaseyKlimkowsky: Ya; ketika kode dan komentar tidak setuju, setidaknya salah satunya salah. Dalam hal ini, saya pikir itu hanya satu - kode; seringkali, sebenarnya keduanya yang salah. Terima kasih telah menunjukkannya; Saya sudah memperbaikinya.
Jonathan Leffler
8

Anda dapat menggunakan temukan opsi keluaran yang dipisahkan nol dengan membaca untuk beralih ke struktur direktori dengan aman.

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

Jadi untuk kasusmu

#!/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

selain itu

#!/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)

akan menjalankan loop sementara dalam lingkup skrip (proses) saat ini dan memungkinkan keluaran find digunakan dalam pengaturan variabel jika diperlukan

James
sumber
2
$'\0'adalah cara menulis yang aneh ''. Kau hilang IFS=dan -rberalih ke read: pernyataan membaca Anda harus: IFS= read -rd '' file.
gniourf_gniourf
Saya pikir beberapa akan perlu mencari $'\0'dan menyebar beberapa titik tumpukan di sekitar. Akan mengedit yang Anda tunjukkan. Apa dampak buruk dari tidak IFS=mencoba echo -e "ok \nok\0" | while read -d '' line; do echo -e "$line"; donetampaknya tidak ada. Juga -r saya lihat sering default, tetapi tidak dapat menemukan contoh untuk apa yang mencegahnya terjadi.
James
4
IFS=diperlukan jika nama file berakhir dengan spasi: coba adalah dengan touch 'Prospero '(perhatikan spasi tambahan). Anda juga perlu -rberalih jika nama file memiliki garis miring terbalik: coba dengan touch 'Prospero\n'.
gniourf_gniourf
-1

Sepertinya Anda mencoba menjalankan file windows (.exe) Tentunya Anda harus menggunakan PowerShell. Pokoknya pada bash shell Linux cukup satu-liner akan cukup.

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

Atau dalam sebuah bash

#!/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
pengguna3183111
sumber