Tìm tất cả các tệp có phần mở rộng đã cho có tên cơ sở là tên của thư mục mẹ


9

Tôi muốn tìm đệ quy cho mọi *.pdftệp trong một thư mục ~/foocó tên cơ sở khớp với tên của thư mục mẹ của tệp.

Ví dụ, giả sử rằng cấu trúc thư mục ~/footrông như thế này

foo
├── dir1
│   ├── dir1.pdf
│   └── dir1.txt
├── dir2
│   ├── dir2.tex
│   └── spam
│       └── spam.pdf
└── dir3
    ├── dir3.pdf
    └── eggs
        └── eggs.pdf

Chạy lệnh mong muốn của tôi sẽ trở lại

~/foo/dir1/dir1.pdf
~/foo/dir2/spam/spam.pdf
~/foo/dir3/dir3.pdf
~/foo/dir3/eggs/eggs.pdf

Điều này có thể sử dụng findhoặc một số tiện ích cốt lõi khác? Tôi cho rằng điều này có thể thực hiện được bằng cách sử dụng -regextùy chọn này findnhưng tôi không chắc làm thế nào để viết đúng mẫu.


Vâng, tôi sẽ chế giễu một ví dụ bây giờ.
Brian Fitzpatrick

1
@Inian Thêm một ví dụ. Không giúp đỡ à?
Brian Fitzpatrick

Câu trả lời:


16

Với GNU find:

find . -regextype egrep -regex '.*/([^/]+)/\1\.pdf'
  • -regextype egrep sử dụng regex phong cách egrep.
  • .*/ phù hợp với chỉ thị cha mẹ lớn.
  • ([^/]+)/ phù hợp với cha mẹ dir trong một nhóm.
  • \1\.pdfsử dụng backreferenceđể khớp tên tập tin như thư mục cha.

cập nhật

Một người (bản thân tôi vì một người) có thể nghĩ rằng .*đủ tham lam, không cần thiết phải loại trừ /khỏi kết hợp cha mẹ:

find . -regextype egrep -regex '.*/(.+)/\1\.pdf'

Lệnh trên sẽ không hoạt động tốt, bởi vì nó toán học ./a/b/a/b.pdf:

  • .*/ diêm ./
  • (.+)/ diêm a/b/
  • \1.pdf diêm a/b.pdf

Rất tuyệt. Chúc tôi có thể regex điều này tốt.
Brian Fitzpatrick

Hoặc find . -regex '.*/\([^/]*\)/\1\.pdf'và sau đó nó thậm chí sẽ hoạt động với BSD find.
Stéphane Chazelas

7

Biến thể vòng lặp truyền thống của việc find .. -exec sh -c ''sử dụng các cấu trúc shell để khớp với tên cơ sở và đường dẫn ngay lập tức ở trên sẽ được thực hiện bên dưới.

find foo/ -name '*.pdf' -exec sh -c '
    for file; do 
        base="${file##*/}"
        path="${file%/*}"
        if [ "${path##*/}" =  "${base%.*}" ]; then
            printf "%s\n" "$file" 
        fi
    done' sh {} +

Để phân tích các mở rộng tham số riêng lẻ

  • filechứa đường dẫn đầy đủ của .pdftệp được trả về từ findlệnh
  • "${file##*/}"chỉ chứa phần sau cùng, /tức là chỉ tên cơ sở của tệp
  • "${file%/*}"chứa đường dẫn đến cuối cùng, /ngoại trừ phần tên cơ sở của kết quả
  • "${path##*/}"chứa phần sau cuối /từ pathbiến, tức là đường dẫn thư mục ngay phía trên tên cơ sở của tệp
  • "${base%.*}"chứa một phần của tên cơ sở với .pdfphần mở rộng bị loại bỏ

Vì vậy, nếu tên cơ sở không có phần mở rộng khớp với tên của thư mục ngay lập tức ở trên, chúng tôi sẽ in đường dẫn.


7

Mặt trái của câu trả lời của Inian , tức là tìm các thư mục, sau đó xem liệu họ có giữ một tệp có tên cụ thể không.

Sau đây in tên đường dẫn của các tệp tìm thấy liên quan đến thư mục foo:

find foo -type d -exec sh -c '
    for dirpath do
        pathname="$dirpath/${dirpath##*/}.pdf"
        if [ -f "$pathname" ]; then
            printf "%s\n" "$pathname"
        fi
    done' sh {} +

${dirpath##*/}sẽ được thay thế bằng phần tên tệp của đường dẫn thư mục và có thể được thay thế bằng $(basename "$dirpath").

Đối với những người thích cú pháp ngắn mạch:

find foo -type d -exec sh -c '
    for dirpath do
        pathname="$dirpath/${dirpath##*/}.pdf"
        [ -f "$pathname" ] && printf "%s\n" "$pathname"
    done' sh {} +

Lợi ích của việc làm theo cách này là bạn có thể có nhiều tệp PDF hơn thư mục. Số lượng thử nghiệm liên quan sẽ giảm nếu một người hạn chế truy vấn bằng số lượng nhỏ hơn (số lượng thư mục).

Ví dụ: nếu một thư mục chứa 100 tệp PDF, điều này sẽ chỉ cố gắng phát hiện một trong số chúng chứ không phải kiểm tra tên của tất cả 100 tệp so với thư mục đó.


3

với zsh:

printf '%s\n' **/*/*.pdf(e@'[[ $REPLY:t = $REPLY:h:t.pdf ]]'@)

Coi chừng rằng **/sẽ không theo symlink, */sẽ.


2

Nó không được chỉ định, nhưng đây là một giải pháp không có biểu thức chính quy nếu có ai quan tâm.

Chúng ta có thể sử dụng find . -type fđể chỉ nhận các tập tin, sau đó sử dụng dirnamebasenameviết điều kiện. Các tiện ích có hành vi sau:

$ find . -type f
./dir2/spam/spam.pdf
./dir2/dir2.tex
./dir3/dir3.pdf
./dir3/eggs/eggs.pdf
./dir1/dir1.pdf
./dir1/dir1.txt

basenamechỉ trả về tên tệp sau lần cuối /:

$ for file in $(find . -type f); do basename $file; done
spam.pdf
dir2.tex
dir3.pdf
eggs.pdf
dir1.pdf
dir1.txt

dirnameđưa toàn bộ đường dẫn đến trận chung kết /:

$ for file in $(find . -type f); do dirname $file; done
./dir2/spam
./dir2
./dir3
./dir3/eggs
./dir1
./dir1

Do đó, basename $(dirname $file)đưa ra thư mục cha của tập tin.

$ for file in $(find . -type f); do basename $(dirname $file) ; done
spam
dir2
dir3
eggs
dir1
dir1

Giải pháp

Kết hợp những điều trên để tạo thành điều kiện "$(basename $file)" = "$(basename $(dirname $file))".pdf, sau đó chỉ in từng kết quả findnếu điều kiện đó trả về đúng.

$ while read file; do if [ "$(basename "$file")" = "$(basename "$(dirname "$file")")".pdf ]; then echo $file; fi done < <(find . -type f)
./dir2/spam/spam.pdf
./dir3/dir3.pdf
./dir3/eggs/eggs.pdf
./dir1/dir1.pdf
./Final Thesis/grits/grits.pdf
./Final Thesis/Final Thesis.pdf

Trong ví dụ trên, chúng tôi đã thêm một thư mục / tệp có khoảng trắng trong tên để xử lý trường hợp đó (cảm ơn @Kusalananda trong các bình luận)


Điều này sẽ không may phá vỡ tên tập tin như Final Thesis.pdf(với một khoảng trắng).
Kusalananda

@Kusalananda Đã sửa.
dùng1717828

0

Tôi thực hiện bash globalbing, vòng lặp đơn giản qua các bài kiểm tra chuỗi bất kỳ ngày nào trong chương trình Tìm kiếm . Gọi tôi là không hợp lý, và trong khi nó có thể là tối ưu dưới mức mã đơn giản như vậy thực hiện mẹo cho tôi: có thể đọc và tái sử dụng, đáp ứng ngay cả!. Do đó, cho phép tôi đề xuất kết hợp:

• bash globalstar : for f in ** ; do ... ** vòng lặp trên mỗi tệp trong thư mục hiện tại và tất cả các thư mục con .. để kiểm tra trạng thái sao trên phiên hiện tại của bạn : shopt -p globstar. Để kích hoạt globalstar : shopt -s globstar.

• "file" utlity : if [[ $(file "$f") =~ pdf ]]; then ... để kiểm tra định dạng tệp thực tế cho pdf - mạnh mẽ hơn so với chỉ kiểm tra phần mở rộng của tệp

• basename, dirname : để so sánh tên tệp với tên của thư mục ngay phía trên nó. basenametrả về tên tệp - dirnametrả về toàn bộ đường dẫn thư mục - kết hợp hai hàm để chỉ trả về một thư mục chứa tệp phù hợp. Tôi đặt từng cái vào một biến ( _mydir_myf ) để sau đó thực hiện một thử nghiệm đơn giản bằng cách sử dụng = ~ để khớp chuỗi.

Một tính linh hoạt: xóa bất kỳ "dấu chấm" nào trong tên tệp để tránh khớp tên tệp với thư mục hiện tại có phím tắt cũng là "." - Tôi đã sử dụng thay thế chuỗi trực tiếp trên biến _myf : ${_myf//./}- không thanh lịch lắm nhưng nó hoạt động. Kết quả trùng khớp sẽ trả về đường dẫn của mỗi tệp - cùng với đường dẫn đầy đủ của thư mục hiện tại bằng cách đi trước đầu ra với : $(pwd)/.

for f in ** ; do
  if [[ $(file "$f") =~ PDF ]]; then
    _mydir="$(basename $(dirname $f))" ; 
    _myf="$(basename $f)" ; 
    [[ "${_myf//./}" =~ "$_mydir" ]] && echo -e "$(pwd)/$f" ; 
  fi ; 
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.