コマンドラインから複数のファイルの拡張子を再帰的に変更するにはどうすればよいですか?


回答:


85

移植可能な方法(POSIX準拠のシステムで動作します):

find /the/path -depth -name "*.abc" -exec sh -c 'mv "$1" "${1%.abc}.edefg"' _ {} \;

bash4では、globstarを使用して再帰的なグロブ(**)を取得できます。

shopt -s globstar
for file in /the/path/**/*.abc; do
  mv "$file" "${file%.abc}.edefg"
done

renameUbuntu の(perl)コマンドは、perl正規表現構文を使用してファイルの名前を変更できます。この構文は、globstarまたはと組み合わせることができますfind

# Using globstar
shopt -s globstar
files=(/the/path/**/*.abc)  

# Best to process the files in chunks to avoid exceeding the maximum argument 
# length. 100 at a time is probably good enough. 
# See http://mywiki.wooledge.org/BashFAQ/095
for ((i = 0; i < ${#files[@]}; i += 100)); do
  rename 's/\.abc$/.edefg/' "${files[@]:i:100}"
done

# Using find:
find /the/path -depth -name "*.abc" -exec rename 's/\.abc$/.edefg/' {} +

http://mywiki.wooledge.org/BashFAQ/030も参照してください


一つ目は、単純に...それは置き換えられない、古いものの上に新しい拡張機能を追加
user2757729

3
@ user2757729、それはそれを置き換えます。終わりのない"${1%.abc}"場合を$1除いて、値に展開します.abc。シェル文字列操作の詳細については、faq 100を参照してください。
ガイラ

私はこれを〜70kファイルのファイル構造で実行しました...少なくとも私のシェルでは、間違いなくそれを置き換えませんでした。
user2757729

1
@ user2757729おそらく「abc」を残したでしょう${1%.abc}...
kvnn

1
他の誰かが最初のコマンドでアンダースコアが何をしているのか疑問に思っている場合はsh -c 、最初の引数が$ 0に割り当てられ、残りの引数が位置パラメータに割り当てられているため必要です。したがって、_は仮引数であり、 '{}'はに対する最初の位置引数sh -cです。
hellobenallan

48

これは、すべてのファイルが同じフォルダーにある場合に必要なタスクを実行します

rename 's/.abc$/.edefg/' *.abc

ファイルの名前を再帰的に変更するには、次を使用します。

find /path/to/root/folder -type f -name '*.abc' -print0 | xargs -0 rename 's/.abc$/.edefg/'

8
またはrename 's/.abc$/.edefg/' /path/to/root/folder/**/*.abc、最新バージョンのBashで。
アダムByrtek

フォルダ内で* .abcを再帰的に使用する方法についてのヒントを与えてくれたAdamに感謝します!
ラファウチェーラク

素晴らしいヒント、ありがとう!正規表現の構文の小さな部分に関する詳細なドキュメントはどこで入手できますか?何のようにs先頭に、そして他のどのような選択肢私はそこで使用することができます。ありがとう!
アント

@AdamByrtek Utopicに関するあなたの提案で失敗しました。(「そのようなファイルはありません」など)
ジョナサンY.

14

再帰的な名前変更の問題の1つは、ファイルの検索に使用する方法が何であれrename、ファイル名だけでなく、パス全体をに渡すことです。そのため、ネストされたフォルダーで複雑な名前の変更を行うのが難しくなります。

この問題を解決するためにfind-execdirアクションを使用します。の-execdir代わりに使用する場合-exec、指定されたコマンドは、一致したファイルを含むサブディレクトリから実行されます。したがって、パス全体をに渡すのではなく、renameのみを渡し./filenameます。これにより、正規表現を簡単に記述できます。

find /the/path -type f \
               -name '*.abc' \
               -execdir rename 's/\.\/(.+)\.abc$/version1_$1.abc/' '{}' \;

詳細に:

  • -type f ディレクトリではなくファイルのみを探すことを意味します
  • -name '*.abc' は、.abcで終わるファイル名のみに一致することを意味します
  • '{}'-execdir見つかったパスを挿入する場所をマークするプレースホルダーです。スペースとシェル文字を含むファイル名を処理できるようにするには、単一引用符が必要です。
  • -typeおよびの後ろのバックスラッシュ-nameは、bashの行継続文字です。この例を使用して読みやすくしましたが、コマンドをすべて1行に入力する場合は必要ありません。
  • ただし、-execdir行の最後にバックスラッシュが必要です。セミコロンをエスケープするためにあり、によって実行されたコマンドを終了し-execdirます。楽しい!

正規表現の説明:

  • s/ 正規表現の開始
  • \.\/-execdirが渡す先頭の./と一致します。\を使用して。および/メタキャラクター(注:この部分は、バージョンによって異なりますfind。ユーザー@apolloからのコメントを参照してください)
  • (.+)ファイル名と一致します。括弧は、後で使用するために一致をキャプチャします
  • \.abc ドットをエスケープし、abcと一致します
  • $ 文字列の最後に一致を固定します

  • / 正規表現の「一致」部分の終わりと「置換」部分の始まりをマークします

  • version1_ このテキストをすべてのファイル名に追加します

  • $1かっこでキャプチャしたため、既存のファイル名を参照します。「一致」部分で複数の括弧のセットを使用する場合、ここで$ 2、$ 3などを使用してそれらを参照できます。
  • .abc新しいファイル名は.abcで終わります。ここで「置換」セクションでドットメタキャラクターをエスケープする必要はありません。
  • / 正規表現の終わり

tree --charset=ascii

|-- a_file.abc
|-- Another.abc
|-- Not_this.def
`-- dir1
    `-- nested_file.abc

tree --charset=ascii

|-- version1_a_file.abc
|-- version1_Another.abc
|-- Not_this.def
`-- dir1
    `-- version1_nested_file.abc

ヒント:renameの-nオプションは便利です。ドライランを実行し、変更する名前を表示しますが、変更は行いません。


詳細な説明に感謝しますが、奇妙なことに、これを試してみると--execdir、先頭にパスしなかった./ため\.\/、正規表現の一部を省略できます。私はOSXないUbuntuの上だからかもしれないことがある
アポロ

5

別のポータブルな方法:

find /the/path -depth -type f -name "*.abc" -exec sh -c 'mv -- "$1" "$(dirname "$1")/$(basename "$1" .abc).edefg"' _ '{}' \;

4
Ask Ubuntuへようこそ!これは貴重な答えですが、このコマンドを機能させる方法と理由を説明するために(編集により)拡張することをお勧めします。
エリアケイガン


0

これが私がしたことであり、私が望んでいた通りに機能しました。mvコマンドを使用しました。複数の.3gpファイルがあり、それらをすべて名前を変更したかった.mp4

以下に短いワンライナーを示します。

for i in *.3gp; do mv -- "$i" "ren-$i.mp4"; done

単に現在のディレクトリをスキャンして、すべての.3gpファイルを取得し、(mvを使用して)名前を ren-name_of_file.mp4


これは、ほとんどの人が望んでいるものではないかもしれない名前に変更さfile.3gpfile.3gp.mp4ます。
ムル

@muruしかし、原則はまだ存在します。任意の拡張子を選択し、宛先の拡張子を指定して名前を変更します。.3gpまたは.mp4、ここでは説明のためだけでした。
コピ

構文は優先され、1行で理解しやすいものです。ただし、basename "$i" .mp4「ren- $ i.mp4」の代わりに以前の拡張子を削除するために使用します。
TaoPR

本当です。いい視点ね。
コピ

0

これを実現する簡単な方法を見つけました。多くのファイルの拡張子をjpgからpdfに変更するには、次を使用します。

for file in /path/to; do mv $file $(basename -s jpg $file)pdf ; done


0

ファイルとディレクトリの名前を変更します find -execdir | rename

単に接尾辞を付けるのではなく、ファイルとディレクトリの両方の名前を変更する場合、これは良いパターンです。

PATH="$(echo "$PATH" | sed -E 's/(^|:)[^\/][^:]*//g')" \
  find . -depth -execdir rename 's/findme/replaceme/' '{}' \;

awesome -execdirオプションはcdrenameコマンドを実行する前に、ディレクトリとは異なり-execます。

-depth 親ディレクトリの欠落に伴う潜在的な問題を防ぐために、最初に子、次に親で名前変更が行われることを確認してください。

-execdir 名前変更はベース名以外の入力パスではうまく機能しないため、たとえば次のように失敗します。

rename 's/findme/replaceme/g' acc/acc

PATHので、ハッキングが必要です-execdir:1つの非常に迷惑な欠点がありfind、非常に独断で、で何かをすることを拒否し-execdir、あなたの内の相対パスがあればPATH、環境変数を、例えば./node_modules/.bin、で失敗します:

find:相対パス './node_modules/.bin'はPATH環境変数に含まれています。これは、findの-execdirアクションと組み合わせた場合、安全ではありません。そのエントリを$ PATHから削除してください

参照:「-execdir」アクションの使用が、PATHにあるディレクトリに対して安全でないのはなぜですか?

-execdirPOSIXの GNU find 拡張機能です。renamePerlベースで、renameパッケージに由来します。Ubuntu 18.10。でテスト済み。

先読み回避策の名前を変更する

入力パスがから来ていないfind場合、または相対的なパスの煩わしさが十分にある場合、Perlの先読みを使用して、次のようにディレクトリの名前を安全に変更できます。

git ls-files | sort -r | xargs rename 's/findme(?!.*\/)\/?$/replaceme/g' '{}'

私はのために便利なアナログを発見していない-execdirxargshttps://superuser.com/questions/893890/xargs-change-working-directory-to-file-path-before-executing/915686

sort -r長いパスが同じプレフィックスを持つ短いものの後に来るので、ファイルは、それぞれのディレクトリの後に来ることを保証するために必要とされます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.