私はetckeeperのgitレプリカを持っており、etckeeper
名前はusrkeeper
。./foo-etckeeper-bar
./foo-usrkeeper-bar
関連ファイルを見つけるのは簡単です。
% find . -path '*etckeeper*' -print
しかし、実際に名前を変更する方法がわかりません。私は次のものxargs
と組み合わせようとしましたmv
。
% find . -path '*etckeeper*' -print0 | xargs -0 -n 1 -J % bash -c mv % '$(echo' % \| sed \"s/etckeeper/usrkeeper/\" \)
読みやすくするためにエスケープされていない後半は、次のように表示されますxargs -0 -n 1 -J % bash -c mv % $(echo % | sed "s/etckeeper/usrkeeper/" )
。これに関するアイデアは、置き換えのために$()
ファイル名をパイプすることですsed
。
ここでの問題は、bash -c
実行に必要なコマンドが単一の文字列であることです。その後、パラメータを位置パラメータとして解釈し始めます。私はすべての内容を引用することができます:
% find . -path '*etckeeper*' -print0 | xargs -0 -n 1 -J % bash -c 'mv % $(echo % | sed "s/etckeeper/usrkeeper/g" )'
しかし、今はxargs
交換されません%
。この問題をどのように解決できますか? (また、参照として、etckeeper
名前を含むファイルが name を含むディレクトリに存在する場合、ディレクトリがファイルの前に移動されるetckeeper
ため、上記の操作は失敗します。)
答え1
名前変更コマンドを使用できます(参照:編集1)。
ソリューション1
合理的な数のファイル/ディレクトリの場合は、bash 4オプションを設定してグローバルスター(再帰名には適用されません。編集3):
shopt -s globstar
rename -n 's/etckeeper/userkeeper/g' **
ソリューション2
ファイル/ディレクトリの数が多い場合は、名前変更と検索を2つの手順で使用して、名前を変更したばかりのディレクトリでファイルの名前変更が失敗するのを防ぎます(参照)。編集2):
find . -type f -exec rename 's/etckeeper/userkeeper/g' {} \;
find . -type d -exec rename 's/etckeeper/userkeeper/g' {} \;
編集1:
2つの名前変更コマンドがあります。。この答えは、Debianベースのディストリビューションで利用可能なPerlベースの名前変更コマンドを使用します。 Debian ベースではなくディストリビューションにインストールするには、次のものが必要です。cpanからインストール可能またはキャッチ。
編集2:
-depth
Jeff Schallerがコメントでfindオプションを指摘したように、オプションProcess each directory's contents before the directory itself
ごとに「ソートされた」ルックアップで-depth
十分です。
find . -depth -exec rename 's/etckeeper/userkeeper/g' {} \;
編集3
etckeeper/etckeeper/etckeeper
解決策1は、外部レベルが内部レベルよりも前に処理され、内部レベルへのポインタが役に立たなくなるため、再帰的な名前変更ターゲット(たとえば)には機能しません。 (最初の名前の後にetckeeper/etckeeper/etckeeper
名前が付けられるため、usrkeeper/etckeeper/etckeeper
名前をetckeeper/etckeeper/
およびに置き換えるとetckeeper/etckeeper/etckeeper
失敗します)。オプションはfind
同じ問題を解決します-depth
。
編集4
Casがコメントで指摘したように、{}\;代わりに {}+ を使用します。 - Perlスクリプトをフォークすると(たとえば、ファイル/ディレクトリごとに1回)、費用がかかります。
答え2
あなたの元の質問は実際に答えやすいです。 (xargs
少なくともOS Xでは)-I
以下を行うオプションもあります。いいえ代替文字列は唯一のパラメータでなければなりません。したがって、呼び出しは次のようになります。
% find . -path '*etckeeper*' -print0 | xargs -0 -n 1 -I % bash -c 'echo mv % $(echo % | sed "s/etckeeper/usrkeeper/g" )'
とても簡単です。それでは、正しい順序で名前を変更しましょう。ディレクトリが最初に印刷されることがわかりましたfind
(下降する前にディレクトリを処理する必要があるため)。したがって、私たちがしなければならないのは、ファイル名の順序を変更することだけです。
% find . -path '*etckeeper*' -print | tail -r | xargs -n 1 -I % bash -c 'echo mv % $(echo % | sed "s/etckeeper/usrkeeper/g" )'
find -print0 | xargs -0
に切り替えましたfind -print | xargs
。なぜですか?tail -r
反転はヌル文字ではなく改行文字に基づいているためです。切り替えないとtail -r
パイプしたのと同じ内容が印刷されます!したがって、スクリプトはより正確になりましたが、改行文字を含むファイル名でも中断されます。
お持ちでない場合は、tail -r
一度お試しくださいtac
。望むより:ファイルの行の順序を変更するには?スタックオーバーフローに。
今問題は、そのsed
命令があまりにも攻撃的だという点だ。たとえば、次のツリーがあります。
.
├── etckeeper-foo
│ └── etckeeper-bar.md
└── some-other-directory
└── etckeeper-baz.md
mv
次のような電話がかかります。
mv ./some-other-directory/etckeeper-baz.md ./some-other-directory/usrkeeper-baz.md
mv ./etckeeper-foo/etckeeper-bar.md ./usrkeeper-foo/usrkeeper-bar.md
mv ./etckeeper-foo ./usrkeeper-foo
最初のものは素晴らしかったが、2番目のものは明らかではなかった。まだ終わっていないからですmv ./etckeeper-foo ./usrkeeper-foo
!
私たちは交換するだけです最後パス名コンポーネント。basename
これを使用してこれを行うことができますdirname
。
% find . -name '*etckeeper*' -print | tail -r | xargs -n 1 -I % bash -c 'echo mv % $(dirname %)/$(basename % | sed "s/etckeeper/usrkeeper/g" )'
私が変更したもう一つのことはfind -name
。find -path
これにより、正しいmv
呼び出しが生成されます。
mv ./some-other-directory/etckeeper-baz.md ./some-other-directory/usrkeeper-baz.md
mv ./etckeeper-foo/etckeeper-bar.md ./etckeeper-foo/usrkeeper-bar.md
mv ./etckeeper-foo ./usrkeeper-foo
ついに。もう一度覚えてください。失敗する難解なファイル名に関して。また、ファイル名が特に長い場合、パラメータが長すぎてxargs
機能しません。mv
この場合、(少なくともOS Xでは)toを渡して-x
すぐにxargs
失敗するように指示できます。
答え3
for
別の解決策は、検索結果を繰り返し、mv
見つかったファイルでbash文字列の置き換えを実行する小さなスクリプトを使用することです。
IFS=$'\n'
for files in $(find . -name "*etckeeper*");
do
mv "$files ${files/etckeeper/usrkeeper}"
done
スクリプトでそれを使用しない場合は、元のIFSを保存し、ループの終わりに復元することをお勧めします。