Cách lặp qua các tệp trong thư mục và thay đổi đường dẫn và thêm hậu tố vào tên tệp


562

Tôi cần viết một kịch bản khởi động chương trình của mình bằng các đối số khác nhau, nhưng tôi mới biết về Bash. Tôi bắt đầu chương trình của mình với:

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

Đây là mã giả cho những gì tôi muốn làm:

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

Vì vậy, tôi thực sự bối rối làm thế nào để tạo đối số thứ hai từ đối số thứ nhất, vì vậy nó trông giống như dataABCD_Log1.txt và bắt đầu chương trình của tôi.

Câu trả lời:


736

Một vài lưu ý đầu tiên: khi bạn sử dụng Data/data1.txtlàm đối số, nó có thực sự phải /Data/data1.txt(với dấu gạch chéo hàng đầu) không? Ngoài ra, chỉ nên quét vòng ngoài cho các tệp .txt hoặc tất cả các tệp trong / Dữ liệu? Đây là một câu trả lời, giả sử /Data/data1.txtvà các tệp .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

Ghi chú:

  • /Data/*.txtmở rộng tới đường dẫn của tệp văn bản trong / Dữ liệu ( bao gồm / Dữ liệu / phần)
  • $( ... ) chạy một lệnh shell và chèn đầu ra của nó tại điểm đó trong dòng lệnh
  • basename somepath .txtxuất ra phần cơ sở của somepath, với .txt bị xóa khỏi phần cuối (ví dụ: /Data/file.txt> file)

Nếu bạn cần chạy MyProgram Data/file.txtthay vì /Data/file.txt, hãy sử dụng "${filename#/}"để xóa dấu gạch chéo hàng đầu. Mặt khác, nếu bạn thực sự Datakhông /Datamuốn quét, chỉ cần sử dụng for filename in Data/*.txt.


1
Rõ ràng basename -slà một phần mở rộng không chuẩn - tôi sẽ chỉnh sửa câu trả lời của mình để sử dụng cú pháp chuẩn.
Gordon Davisson

21
Nếu không có tệp nào được tìm thấy / khớp với ký tự đại diện, tôi sẽ tìm thấy khối thực thi vòng lặp vẫn được nhập một lần với filename = "/Data/*.txt". Làm thế nào tôi có thể tránh điều này?
Oliver Pearmain 3/2/2015

20
@OliverPearmain Hoặc sử dụng shopt -s nullglobtrước vòng lặp (và shopt -u nullglobsau để tránh các sự cố sau này) hoặc thêm if [[ ! -e "$filename ]]; then continue; fivào đầu vòng lặp, vì vậy nó sẽ bỏ qua các tệp không tồn tại.
Gordon Davisson

9
Điều này không hoạt động khi có các tệp chứa khoảng trắng trong tên của chúng.
Isa Hassen

4
@Isa Nó nên hoạt động với khoảng trắng, miễn là tất cả các dấu ngoặc kép được đặt đúng chỗ. Để lại bất kỳ dấu ngoặc kép nào và bạn sẽ gặp vấn đề với khoảng trắng.
Gordon Davisson

345

Xin lỗi vì đã xử lý luồng, nhưng bất cứ khi nào bạn lặp lại các tệp bằng cách tạo khối, thì tốt nhất là tránh trường hợp góc mà quả cầu không khớp (điều này làm cho biến vòng lặp mở rộng sang chính chuỗi mẫu không khớp).

Ví dụ:

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

Tham khảo: Cạm bẫy Bash


8
Đây vẫn là một cảnh báo kịp thời. Tôi nghĩ rằng tôi đã tạo tập lệnh của mình không chính xác, nhưng tôi có phần mở rộng tập tin của tôi thay vì chữ in hoa, nó không tìm thấy tập tin nào và trả về mẫu hình cầu. ừm
RufusVS

5
Vì thẻ Bash được sử dụng: đó là những gì shopt nullglobdành cho! (hoặc shopt failglobcó thể được sử dụng quá, tùy thuộc vào hành vi bạn muốn).
gniourf_gniourf

Ngoài ra, điều này cũng kiểm tra các tệp đã bị xóa trong quá trình xử lý, trước khi vòng lặp đến chúng.
loxaxs

1
Cũng xử lý trường hợp bạn có một thư mục có têndir.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

Sự name=${file##*/}thay thế ( mở rộng tham số shell ) loại bỏ tên đường dẫn hàng đầu cho đến cuối cùng /.

Sự base=${name%.txt}thay thế loại bỏ dấu vết .txt. Sẽ khó hơn một chút nếu các phần mở rộng có thể thay đổi.


3
Tôi tin rằng có một lỗi trong mã của bạn. base=${name%.txt}Thay vào đó, một dòng nên thay thế base=${base%.txt}.
caseklim

4
@CaseyKlimkowsky: Vâng; khi mã và các ý kiến ​​không đồng ý, ít nhất một trong số đó là sai. Trong trường hợp này, tôi nghĩ rằng nó chỉ là một - mã; thông thường, nó thực sự là cả hai đều sai. Cảm ơn đã chỉ ra rằng; Tôi đã sửa nó.
Jonathan Leffler

8

Bạn có thể sử dụng tùy chọn đầu ra được phân tách bằng null với đọc để lặp lại các cấu trúc thư mục một cách an toàn.

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

Vì vậy, cho trường hợp của bạn

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

Ngoài ra

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

sẽ chạy vòng lặp while trong phạm vi hiện tại của tập lệnh (process) và cho phép đầu ra của find được sử dụng trong việc thiết lập các biến nếu cần


2
$'\0'là một cách viết kỳ lạ ''. Bạn đang thiếu IFS=-rchuyển sang read: tuyên bố đọc của bạn phải là : IFS= read -rd '' file.
gniourf_gniourf

Tôi nghĩ rằng một số sẽ cần tìm kiếm $'\0'và trải một số điểm stack xung quanh. Đi để thực hiện các chỉnh sửa bạn chỉ ra. Những ảnh hưởng xấu của việc không IFS=thử echo -e "ok \nok\0" | while read -d '' line; do echo -e "$line"; donecó vẻ như không có. Ngoài ra -r tôi thấy thường mặc định, nhưng không thể tìm thấy một ví dụ cho những gì nó ngăn chặn xảy ra.
James

4
IFS=là cần thiết trong trường hợp tên tệp kết thúc bằng dấu cách: hãy thử với touch 'Prospero '(lưu ý dấu cách). Ngoài ra, bạn cần -rchuyển đổi trong trường hợp tên tệp có dấu gạch chéo ngược: hãy thử với touch 'Prospero\n'.
gniourf_gniourf

-1

Có vẻ như bạn đang cố thực thi một tệp windows (.exe) Chắc chắn bạn phải sử dụng powershell. Dù sao trên vỏ bash Linux, một lớp lót đơn giản sẽ đủ.

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

Hoặc trong một 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.