Đổi tên hàng loạt tệp bằng Bash


101

Làm thế nào Bash có thể đổi tên một loạt các gói để xóa số phiên bản của chúng? Tôi đã đùa giỡn với cả hai expr%%vô ích.

Ví dụ:

Xft2-2.1.13.pkg trở thành Xft2.pkg

jasper-1.900.1.pkg trở thành jasper.pkg

xorg-libXrandr-1.2.3.pkg trở thành xorg-libXrandr.pkg


1
Tôi định sử dụng điều này thường xuyên, như một kịch bản viết một lần sử dụng nhiều. Bất kỳ hệ thống nào tôi đang sử dụng sẽ có cơ sở dữ liệu về nó, vì vậy tôi không sợ những điều cơ bản khá hữu ích.
Jeremy L

Câu trả lời:


190

Bạn có thể sử dụng tính năng mở rộng tham số của bash

for i in ./*.pkg ; do mv "$i" "${i/-[0-9.]*.pkg/.pkg}" ; done

Dấu ngoặc kép là cần thiết cho tên tệp có khoảng trắng.


1
Tôi cũng thích nó, nhưng nó sử dụng các tính năng dành riêng cho bash ... Tôi thường không sử dụng chúng để ủng hộ sh "tiêu chuẩn".
Diego Sevilla

2
Đối với những thứ tương tác một dòng vứt đi, tôi luôn sử dụng nó thay vì sed-fu. Nếu đó là một tập lệnh, vâng, hãy tránh các bản gốc.
richq

Một vấn đề tôi đã tìm thấy với mã: Điều gì xảy ra nếu các tệp đã được đổi tên và tôi chạy lại? Tôi nhận được cảnh báo khi cố gắng di chuyển vào một thư mục con của chính nó.
Jeremy L

1
Để tránh lỗi chạy lại, bạn có thể sử dụng cùng một mẫu trong phần " .pkg", tức là "for i in * - [0-9.] .Pkg; do mv $ i $ {i / - [0-9 .] *. pkg / .pkg}; xong ". Nhưng các lỗi là vô hại (chuyển sang cùng một tệp).
richq

3
Không phải là một giải pháp bash, nhưng nếu bạn đã perl được cài đặt, nó cung cấp các lệnh đổi tên thuận tiện cho việc đổi tên hàng loạt, chỉ cần làmrename "s/-[0-9.]*//" *.pkg
Rufus

33

Nếu tất cả các tệp nằm trong cùng một thư mục, trình tự

ls | 
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' | 
sh

sẽ làm công việc của bạn. Lệnh sed sẽ tạo ra một chuỗi lệnh mv , sau đó bạn có thể đưa vào shell. Trước tiên, tốt nhất là chạy đường dẫn mà không có dấu vết | shđể xác minh rằng lệnh có thực hiện những gì bạn muốn.

Để đệ quy qua nhiều thư mục, hãy sử dụng một cái gì đó như

find . -type f |
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' |
sh

Lưu ý rằng trong sed , trình tự nhóm biểu thức chính quy có các dấu ngoặc đứng trước dấu gạch chéo ngược \(\)thay vì các dấu ngoặc đơn ().


Hãy tha thứ cho sự ngây thơ của tôi, nhưng việc thoát khỏi dấu ngoặc đơn với dấu gạch chéo ngược sẽ khiến chúng được coi như những ký tự chữ?
defos1

2
Trong sed biểu thức chính quy nhóm chuỗi là (, chứ không phải là một khung duy nhất.
Diomidis Spinellis

không hiệu quả với tôi, rất vui khi nhận được sự giúp đỡ của bạn .... Hậu tố của tệp FROM được thêm vào tệp TO: $ find. -loại d | sed -n 's /(.*)\/(.*) anysoftkeyboard (. *) / mv "\ 1 \ / \ 2anysoftkeyboard \ 3" "\ 1 \ / \ 2effectedkeyboard \ 3" / p' | sh >> >>>>> OUTPUT >>>> mv: đổi tên ./base/src/main/java/com/anysoftkeyboard/base/dictionaries thành ./base/src/main/java/com/effectedkeyboard/base/dictionaries/dictionaries : Không có tập tin hoặc thư mục
Vitali Pom

Có vẻ như bạn đang thiếu dấu gạch chéo ngược trong dấu ngoặc.
Diomidis Spinellis

1
Không sử dụng lstrong tập lệnh. Sự đa dạng đầu tiên có thể đạt được printf '%s\n' * | sed ...và với một số sáng tạo, bạn cũng có thể loại bỏ sự đa dạng sedđó. Bash's printfcung cấp một '%q'mã định dạng có thể hữu ích nếu bạn có tên tệp chứa siêu ký tự vỏ theo nghĩa đen.
ba

10

Tôi sẽ làm một cái gì đó như thế này:

for file in *.pkg ; do
    mv $file $(echo $file | rev | cut -f2- -d- | rev).pkg
done

cho rằng tất cả tệp của bạn đều nằm trong thư mục hiện tại. Nếu không, hãy thử sử dụng find như lời khuyên ở trên của Javier.

CHỈNH SỬA : Ngoài ra, phiên bản này không sử dụng bất kỳ tính năng dành riêng cho bash nào như những tính năng khác ở trên, điều này dẫn bạn đến tính di động nhiều hơn.


Tất cả chúng đều nằm trong cùng một thư mục. Bạn nghĩ gì về câu trả lời của rq?
Jeremy L

Tôi không muốn sử dụng các tính năng dành riêng cho bash mà thay vào đó là sh tiêu chuẩn hơn.
Diego Sevilla

1
Tôi cho rằng điều này là để đổi tên nhanh chóng, không phải để viết nền tảng chéo thế hệ tiếp theo, Ứng dụng doanh nghiệp di động ;-)
richq

1
Haha, đủ công bằng ... Chỉ từ một người đàn ông có đầu óc linh động như tôi :)
Diego Sevilla

Cái này không giải thích cho ví dụ thứ ba: xorg-libXrandr (gạch nối được sử dụng trong tên).
Jeremy L

5

Đây là một POSIX gần tương đương với câu trả lời hiện được chấp nhận . Điều này giao dịch ${variable/substring/replacement}mở rộng tham số chỉ Bash cho một tham số có sẵn trong bất kỳ trình bao tương thích với Bourne nào.

for i in ./*.pkg; do
    mv "$i" "${i%-[0-9.]*.pkg}.pkg"
done

Việc mở rộng tham số ${variable%pattern}tạo ra giá trị variablevới bất kỳ hậu tố nào phù hợp với patternloại bỏ. (Ngoài ra còn ${variable#pattern}phải xóa một tiền tố.)

Tôi đã giữ -[0-9.]*câu trả lời phụ từ câu trả lời được chấp nhận mặc dù nó có thể gây hiểu lầm. Nó không phải là một biểu thức chính quy, mà là một mẫu hình cầu; vì vậy nó không có nghĩa là "một dấu gạch ngang theo sau là không hoặc nhiều số hoặc dấu chấm". Thay vào đó, nó có nghĩa là "dấu gạch ngang, theo sau là số hoặc dấu chấm, theo sau là bất kỳ thứ gì". "Bất cứ điều gì" sẽ là trận đấu ngắn nhất có thể, không phải là dài nhất. (Bash cung cấp ##%%để cắt tiền tố hoặc hậu tố dài nhất có thể, thay vì ngắn nhất.)


5

Chúng tôi có thể cho rằng sednó có sẵn trên bất kỳ * nix nào, nhưng chúng tôi không thể chắc chắn rằng nó sẽ hỗ trợ sed -ntạo các lệnh mv. ( LƯU Ý: Chỉ GNU mới sedthực hiện điều này.)

Mặc dù vậy, bash nội trang và sed, chúng ta có thể nhanh chóng tạo một hàm shell để thực hiện điều này.

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      mv -v "$file" "$(sed $sed_pattern <<< $file)"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

Sử dụng

sedrename 's|\(.*\)\(-[0-9.]*\.pkg\)|\1\2|' *.pkg

before:

./Xft2-2.1.13.pkg
./jasper-1.900.1.pkg
./xorg-libXrandr-1.2.3.pkg

after:

./Xft2.pkg
./jasper.pkg
./xorg-libXrandr.pkg

Tạo các thư mục đích:

mvkhông tự động tạo các thư mục đích nên chúng tôi không thể sử dụng phiên bản ban đầu của chúng tôi sedrename.

Đó là một thay đổi khá nhỏ, vì vậy sẽ rất tuyệt nếu bao gồm tính năng đó:

Chúng tôi sẽ cần một chức năng tiện ích, abspath(hoặc đường dẫn tuyệt đối) vì bash không có tích hợp này.

abspath () { case "$1" in
               /*)printf "%s\n" "$1";;
               *)printf "%s\n" "$PWD/$1";;
             esac; }

Khi chúng ta có điều đó, chúng ta có thể tạo (các) thư mục đích cho một mẫu sed / rename bao gồm cấu trúc thư mục mới.

Điều này sẽ đảm bảo chúng tôi biết tên của các thư mục mục tiêu của chúng tôi. Khi chúng tôi đổi tên, chúng tôi sẽ cần sử dụng nó trên tên tệp đích.

# generate the rename target
target="$(sed $sed_pattern <<< $file)"

# Use absolute path of the rename target to make target folder structure
mkdir -p "$(dirname $(abspath $target))"

# finally move the file to the target name/folders
mv -v "$file" "$target"

Đây là tập lệnh nhận biết thư mục đầy đủ ...

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      target="$(sed $sed_pattern <<< $file)"
      mkdir -p "$(dirname $(abspath $target))"
      mv -v "$file" "$target"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

Tất nhiên, nó vẫn hoạt động khi chúng ta không có các thư mục đích cụ thể.

Nếu chúng tôi muốn đặt tất cả các bài hát vào một thư mục, ./Beethoven/chúng tôi có thể làm như sau:

Sử dụng

sedrename 's|Beethoven - |Beethoven/|g' *.mp3

before:

./Beethoven - Fur Elise.mp3
./Beethoven - Moonlight Sonata.mp3
./Beethoven - Ode to Joy.mp3
./Beethoven - Rage Over the Lost Penny.mp3

after:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

Phần thưởng vòng ...

Sử dụng tập lệnh này để di chuyển tệp từ các thư mục vào một thư mục:

Giả sử chúng tôi muốn thu thập tất cả các tệp phù hợp và đặt chúng vào thư mục hiện tại, chúng tôi có thể làm điều đó:

sedrename 's|.*/||' **/*.mp3

before:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

after:

./Beethoven/ # (now empty)
./Fur Elise.mp3
./Moonlight Sonata.mp3
./Ode to Joy.mp3
./Rage Over the Lost Penny.mp3

Lưu ý về các mẫu regex cói

Quy tắc mẫu sed thông thường áp dụng trong tập lệnh này, những mẫu này không phải là PCRE (Biểu thức chính quy tương thích Perl). Bạn có thể có cú pháp biểu thức chính quy mở rộng sed, sử dụng một trong hai sed -rhoặc sed -E tùy thuộc vào nền tảng của bạn.

Xem tuân thủ POSIX man re_formatđể biết mô tả đầy đủ về các mẫu regexp cơ bản và mở rộng của sed.


4

Tôi thấy rằng đổi tên là một công cụ đơn giản hơn nhiều để sử dụng cho loại việc này. Tôi đã tìm thấy nó trên Homebrew cho OSX

Đối với ví dụ của bạn, tôi sẽ làm:

rename 's/\d*?\.\d*?\.\d*?//' *.pkg

'S' có nghĩa là thay thế. Biểu mẫu là s / searchPattern / Replace / files_to_apply. Bạn cần sử dụng regex cho việc này, việc này sẽ mất một ít nghiên cứu nhưng nó rất đáng để nỗ lực.


Tôi đồng ý. Đối với một số việc không thường xuyên, sử dụng for, sedv.v. cũng được, nhưng sử dụng đơn giản và nhanh chóng hơn nhiều renamenếu bạn thấy mình làm việc này thường xuyên.
argentum2f

Đó là bạn đang thoát kinh?
Big Money

Vấn đề là đây không phải là di động; không chỉ không phải tất cả các nền tảng đều có renamelệnh; ngoài ra, một số sẽ có một khác nhau rename lệnh với các tính năng khác nhau, cú pháp, và / hoặc các tùy chọn. Điều này trông giống như Perl renamekhá phổ biến; nhưng câu trả lời thực sự nên làm rõ điều này.
tripleee

3

sử dụng tốt hơn sedcho điều này, một cái gì đó như:

find . -type f -name "*.pkg" |
 sed -e 's/((.*)-[0-9.]*\.pkg)/\1 \2.pkg/g' |
 while read nameA nameB; do
    mv $nameA $nameB;
 done

Việc tìm ra biểu thức chính quy được để lại như một bài tập (cũng như xử lý các tên tệp bao gồm khoảng trắng)


Cảm ơn: Dấu cách không được sử dụng trong tên.
Jeremy L

1

Điều này dường như hoạt động giả sử rằng

  • mọi thứ kết thúc với $ pkg
  • phiên bản # của bạn luôn bắt đầu bằng dấu "-"

lột bỏ .pkg, sau đó lột bỏ - ..

for x in $(ls); do echo $x $(echo $x | sed 's/\.pkg//g' | sed 's/-.*//g').pkg; done

Có một số gói có dấu gạch ngang trong tên của chúng (xem ví dụ thứ ba). Điều này có ảnh hưởng đến mã của bạn không?
Jeremy L

1
Bạn có thể chạy nhiều biểu thức sed bằng cách sử dụng -e. Vd sed -e 's/\.pkg//g' -e 's/-.*//g'.
tacotuesday

Không phân tích cú pháp lsđầu ratrích dẫn các biến của bạn. Xem thêm shellcheck.net để chẩn đoán các sự cố thường gặp và phản vật chất trong các tập lệnh shell.
tripleee

1

Tôi có nhiều *.txttệp được đổi tên như .sqltrong cùng một thư mục. dưới đây đã làm việc cho tôi:

for i in \`ls *.txt | awk -F "." '{print $1}'\` ;do mv $i.txt $i.sql; done

2
Bạn có thể cải thiện câu trả lời của mình bằng cách trừu tượng hóa nó vào trường hợp sử dụng thực tế của OP.
dakab

Điều này có nhiều vấn đề cũng như một lỗi cú pháp rõ ràng. Xem shellcheck.net để bắt đầu.
tripleee

0

Cảm ơn bạn vì câu trả lời này. Tôi cũng có một số loại vấn đề. Di chuyển tệp .nzb.queued sang tệp .nzb. Nó có khoảng trắng và các dấu gạch ngang khác trong tên tệp và điều này đã giải quyết được vấn đề của tôi:

find . -type f -name "*.nzb.queued" |
sed -ne "s/^\(\(.*\).nzb.queued\)$/mv -v \"\1\" \"\2.nzb\"/p" |
sh

Nó dựa trên câu trả lời của Diomidis Spinellis.

Regex tạo một nhóm cho toàn bộ tên tệp và một nhóm cho phần trước .nzb.queued và sau đó tạo lệnh di chuyển shell. Với các chuỗi được trích dẫn. Điều này cũng tránh tạo một vòng lặp trong shell script vì điều này đã được thực hiện bởi sed.


Cần lưu ý rằng điều này chỉ đúng với GNU sed. Trên BSD, sed sẽ không diễn giải -ntùy chọn theo cách tương tự.
ocodo
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.