Tạo tập lệnh BASH `for` xử lý tên tệp có dấu cách (hoặc cách giải quyết)


12

Trong khi tôi đã sử dụng BASH được vài năm, trải nghiệm của tôi với kịch bản BASH tương đối hạn chế.

Mã của tôi là như dưới đây. Nó sẽ lấy toàn bộ cấu trúc thư mục từ trong thư mục hiện tại và sao chép nó vào $OUTDIR.

for DIR in `find . -type d -printf "\"%P\"\040"`
do
  echo mkdir -p \"${OUTPATH}${DIR}\"        # Using echo for debug; working script will simply execute mkdir
  echo Created $DIR
done

Vấn đề là, đây là một mẫu cấu trúc tệp của tôi:

$ ls
Expect The Impossible-Stellar Kart
Five Iron Frenzy - Cheeses...
Five Score and Seven Years Ago-Relient K
Hello-After Edmund
I Will Go-Starfield
Learning to Breathe-Switchfoot
MMHMM-Relient K

Lưu ý các khoảng trắng: -S Và forlấy tham số từng chữ, vì vậy đầu ra của tập lệnh của tôi trông giống như thế này:

Creating directory structure...
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Learning"
Created Learning
mkdir -p "/myfiles/multimedia/samjmusicmp3test/to"
Created to
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Breathe-Switchfoot"
Created Breathe-Switchfoot

Nhưng tôi cần nó để lấy toàn bộ tên tệp (một dòng tại một thời điểm) từ đầu ra của find. Tôi cũng đã thử findđặt dấu ngoặc kép xung quanh mỗi tên tệp. Nhưng điều này không có ích.

for DIR in `find . -type d -printf "\"%P\"\040"`

Và đầu ra với dòng thay đổi này:

Creating directory structure...
mkdir -p "/myfiles/multimedia/samjmusicmp3test/"""
Created ""
mkdir -p "/myfiles/multimedia/samjmusicmp3test/"Learning"
Created "Learning
mkdir -p "/myfiles/multimedia/samjmusicmp3test/to"
Created to
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Breathe-Switchfoot""
Created Breathe-Switchfoot"

Bây giờ, tôi cần một số cách mà tôi có thể lặp lại như thế này, bởi vì tôi cũng muốn chạy một lệnh phức tạp hơn liên quan đến gstreamermỗi tệp trong một cấu trúc tương tự sau. Làm thế nào tôi nên làm điều này?

Chỉnh sửa: Tôi cần một cấu trúc mã cho phép tôi chạy nhiều dòng mã cho mỗi thư mục / tệp / vòng lặp. Xin lỗi nếu tôi không rõ ràng.

Giải pháp: Ban đầu tôi đã thử:

find . -type d | while read DIR
do
  mkdir -p "${OUTPATH}${DIR}"
  echo Created $DIR
done

Điều này làm việc tốt cho hầu hết các phần. Tuy nhiên, sau đó tôi thấy rằng do đường ống dẫn đến vòng lặp while chạy trong một mạng con, nên bất kỳ biến nào được đặt trong vòng lặp sau đó đều không khả dụng khiến việc thực hiện bộ đếm lỗi khá khó khăn. Giải pháp cuối cùng của tôi (từ câu trả lời này trên SO ):

while read DIR
do
  mkdir -p "${OUTPATH}${DIR}"
  echo Created $DIR
done < <(find . -type d)

Điều này sau đó cho phép tôi tăng các biến có điều kiện trong vòng lặp sẽ có sẵn sau này trong tập lệnh.


Why_would_you_ever_need_a_space_in_a_file_name?
Kevin Panko

Đúng, không phải sở thích của tôi. Mặc dù, để xóa khoảng trắng, trước tiên bạn cần xử lý tệp có khoảng trắng;)
Samuel Jaeschke 17/03/2016

1
Trên thực tế, tên tệp nên cho phép không gian. Tôi sẽ cho phép bất cứ điều gì nhưng /và ký tự không thể in được. Nhưng bất cứ điều gì được cho phép ngoại trừ /\0vì vậy bạn phải cho phép chúng.
Kevin Panko

Câu trả lời:


11

Bạn cần đặt ống findthành một whilevòng lặp.

find ... | while read -r dir
do
    something with "$dir"
done

Ngoài ra, bạn sẽ không cần phải sử dụng -printftrong trường hợp này.

Bạn có thể đưa ra bằng chứng này chống lại các tệp có dòng mới trong tên của chúng, nếu bạn muốn, bằng cách sử dụng dấu phân cách nullbyte (đó là ký tự duy nhất không thể xuất hiện trong filepath * nix):

find ... -print0 | while read -d '' -r dir
do
    something with "$dir"
done

Bạn cũng sẽ thấy việc sử dụng $()thay vì backticks trở nên linh hoạt và dễ dàng hơn. Chúng có thể được lồng dễ dàng hơn nhiều và trích dẫn có thể được thực hiện dễ dàng hơn nhiều. Ví dụ giả định này sẽ minh họa những điểm sau:

echo "$(echo "$(echo "hello")")"

Hãy cố gắng làm điều đó với backticks.


2
Ngoài ra, thay vì "$dir", nên sử dụng "${dir}"- thật dễ dàng để phân biệt sự khác biệt giữa tên $ {dir} và $ {dirname}, nhưng $ dirname có thể được hiểu theo một trong hai cách.
James Polley

Điều quan trọng ở đây là readđọc toàn bộ một dòng ${dir}, vì vậy IFS không thành vấn đề.
James Polley

1
Cảm ơn bạn đã tìm thấy lỗi đánh máy $ / ". Niềng răng không cần thiết nếu không có gì theo tên biến.
Tạm dừng cho đến khi có thông báo mới.

4
Điều này sẽ xử lý tên đường dẫn có khoảng trắng (U + 0020), nhưng vẫn không thể xử lý đúng tên đường dẫn với nguồn cấp dữ liệu dòng (U + 000A). Tôi thích find … -print0 | xargs -0 …bởi vì dấu phân cách mà nó sử dụng tương ứng chính xác với ký tự duy nhất không được phép trong các đường dẫn POSIX: NUL (U + 0000).
Chris Johnsen

2
Hoàn hảo! Chỉ cần những gì tôi đang tìm kiếm. Nó chưa bao giờ xảy ra với tôi rằng bạn có thể có thể dẫn đến while. @Chris Johnsen: Đúng, nhưng ngay cả các chương trình trích xuất âm nhạc cũng không có xu hướng đưa các nguồn cấp dữ liệu vào tên tệp của chúng. Và nếu họ làm như vậy, tôi muốn biết (ví dụ: có gì đó không ổn) và loại bỏ chúng ngay lập tức ...
Samuel Jaeschke

8

Xem câu trả lời này tôi đã viết vài ngày trước để biết ví dụ về một kịch bản xử lý tên tệp có dấu cách.

Có một cách phức tạp hơn một chút (nhưng ngắn gọn hơn) để đạt được những gì bạn đang cố gắng thực hiện:

find . -type d -print0 | xargs -0 -I {} mkdir -p ../theredir/{}

-print0nói tìm để tách các đối số bằng null; các -0 đến xargs nói với nó để mong đợi các đối số được phân tách bằng null. Điều này có nghĩa là nó xử lý không gian tốt.

-I {}nói với xargs để thay thế chuỗi {}bằng tên tệp. Điều này cũng ngụ ý rằng chỉ nên sử dụng một tên tệp cho mỗi dòng lệnh (xargs thường sẽ nhét càng nhiều càng phù hợp với dòng)

Phần còn lại nên rõ ràng.


Tuy nhiên, gợi ý của Dennis Williamson là (ngoài lỗi chính tả) dễ đọc hơn nhiều, và do đó thích hợp hơn trong mọi cách.
James Polley

Hoạt động, đối với mkdir, nhưng xin lỗi tôi nên rõ ràng hơn - tôi muốn chạy một loạt các lệnh cho mỗi tệp. Bạn thấy, đối với thói quen tương tự của tôi sau này, tôi muốn tạo một tên tệp đầu ra dựa trên tên tệp đầu vào (bao gồm tước phần mở rộng .ogg và thêm .mp3) và sau đó sử dụng nhiều biến này trong đường ống của tôi khi gọi gst-launch.
Samuel Jaeschke

5

Vấn đề bạn gặp phải là câu lệnh for đang phản hồi tìm kiếm dưới dạng các đối số riêng biệt. Các dấu phân cách không gian. Bạn cần sử dụng biến IFS của bash để không phân chia trên không gian.

Đây là một liên kết giải thích làm thế nào để làm điều này.

Biến nội bộ IFS

Một cách giải quyết vấn đề này là thay đổi biến IFS (Bộ tách trường nội bộ) của Bash để nó phân tách các trường bằng một thứ khác ngoài khoảng trắng mặc định (dấu cách, tab, dòng mới), trong trường hợp này là dấu phẩy.

#!/bin/bash
IFS=$';'

for I in `find -type d -printf \"%P\"\;`
do
   echo "== $I =="
done

Đặt tìm kiếm của bạn để xuất dấu phân cách trường của bạn sau% P và đặt IFS của bạn một cách thích hợp. Tôi đã chọn dấu chấm phẩy vì nó rất khó tìm thấy trong tên tệp của bạn.

Cách khác là gọi mkdir từ tìm trực tiếp qua -execbạn có thể bỏ qua vòng lặp for hoàn toàn. Đó là nếu bạn không cần phải thực hiện bất kỳ phân tích cú pháp bổ sung nào.


Nếu tên tệp chứa IFS thì sao? Sau đó, bạn phải chọn một cái khác. Nhưng sau đó, chuyện gì sẽ xảy ra nếu ...
Tạm dừng cho đến khi có thông báo mới.

3
Bạn có thể chọn /trên POSIX và :trên các hệ thống tập tin DOS. Có các ký tự không hợp lệ cho các hệ thống tệp khác nhau mà bạn có thể chọn cho IFS. Bất cứ điều gì phức tạp hơn và bạn tốt hơn nên sử dụng perl.
Hội trường Darren

2
Vấn đề với việc sử dụng / là đó là dấu phân cách thư mục và findtrả về tên tệp với các đường dẫn bao gồm dấu gạch chéo. Hãy thử thay đổi dấu chấm phẩy trong tập lệnh của bạn thành dấu gạch chéo và echo sẽ in thư mục và tên tệp trên các dòng riêng biệt.
Tạm dừng cho đến khi có thông báo mới.

Điều đó cũng có vẻ khá hữu ích. Tôi đã đi với đường ống để whiletùy chọn, nhưng điều này cũng có vẻ khá khả thi. Vâng, trong cấu trúc tương tự của tôi sau này tôi cần phải phân tích cú pháp thêm. (Các tên tập tin đầu vào sẽ là .ogg, trong đó sẽ được thông qua như filesrctrong các đường ống gst, nhưng một kết thúc tương đương trong .mp3 có trụ sở tại thư mục đầu ra sẽ được tạo ra và cũng truyền cho các đường ống như filesink, và điều này tất nhiên cần phải được thực hiện cho mỗi tệp, cùng với một số echocho người dùng.)
Samuel Jaeschke

4

Nếu phần thân của vòng lặp của bạn nhiều hơn một lệnh, có thể sử dụng xargs để điều khiển tập lệnh shell:

export OUTPATH=/some/where/else/
find . -type d -print0 | xargs -0 bash -c 'for DIR in "$@"; do
  printf "mkdir -p %q\\n" "${OUTPATH}${DIR}"        # Using echo for debug; working script will simply execute mkdir
  echo Created $DIR
done' -

Hãy chắc chắn bao gồm dấu gạch ngang (hoặc một số 'từ' khác) nếu shell thuộc loại Bourne / POSIX (nó được sử dụng để đặt $ 0 trong tập lệnh shell). Ngoài ra, phải cẩn thận với trích dẫn, vì tập lệnh shell đang được viết bên trong một chuỗi trích dẫn thay vì trực tiếp tại dấu nhắc.


Một khái niệm thú vị khác. Cảm ơn - Tôi chắc chắn tôi sẽ tìm thấy cách sử dụng cho việc này sau :)
Samuel Jaeschke

1

trong câu hỏi cập nhật của bạn, bạn có

mkdir -p \"${OUTPATH}${DIR}\"

cái này nên

mkdir -p "${OUTPATH}${DIR}"

Cảm ơn. Đã sửa. Nó cũng đang đọc đến FILENAME thay vì TRỰC TIẾP - sao chép-dán: P
Samuel Jaeschke

1
find . -type d -exec mkdir -p "{}\040" ';' -exec echo "Created {}\040" ';'

0

hoặc để làm cho toàn bộ điều ít phức tạp hơn nhiều:

% rsync -av --include='*/' --exclude='*' SRC DST

điều này sao chép cấu trúc thư mục của SRC vào DST.


Không, tôi cần một cấu trúc lặp như vậy, cho phép tôi chạy nhiều dòng mã cho mỗi tệp. "Bây giờ, tôi cần một số cách mà tôi có thể lặp đi lặp lại như thế này, bởi vì tôi cũng muốn chạy một lệnh phức tạp hơn liên quan đến trình phân luồng trên mỗi tệp theo cấu trúc tương tự sau." Xin lỗi nếu tôi không rõ ràng.
Samuel Jaeschke

lệnh tôi đã đưa ra để giải quyết vấn đề mà bạn yêu cầu, không thành vấn đề nếu đây chỉ là một phần của 'đường ống' lớn hơn về phía bạn. đối với người khác gặp vấn đề như được mô tả trong câu hỏi, phương pháp rsync sẽ hoạt động. vì vậy, không cần phải hối tiếc về sự không rõ ràng tiềm năng :)
akira

Vâng Không, ý tôi là tôi sẽ sử dụng tương tựwhile cấu trúc ... do... donesau này để xử lý tương tự từ find, sẽ yêu cầu một số dòng mã được chạy trên mỗi tệp (sửa đổi chuỗi, echo, gst-launch, v.v. ) và rsyncsẽ không đạt được điều này. Đó là lý do tại sao tôi xác định rằng tôi cần có khả năng chạy một nhóm lệnh phức tạp hơn trong một cấu trúc tương tự. Kịch bản của tôi sử dụng cấu trúc vòng lặp này hai lần, vì vậy đối với câu hỏi tôi đã đăng cái có ít lỗi hơn ở giữa.
Samuel Jaeschke

0

Nếu bạn đã cài đặt GNU Parallel http: // www.gnu.org/software/abul/, bạn có thể làm điều này:

find . -type d | parallel echo making {} ";" mkdir -p /tmp/outdir/{} ";" echo made {}

Xem video giới thiệu về GNU Parallel để tìm hiểu thêm: http://www.youtube.com/watch?v=OpaiGYxkSuQ

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.