たくさんのディレクトリを調べて、_test.rbで終わるすべてのファイルの名前を_spec.rbで終わるように変更します。それは私がbashをどのように扱うべきかをまったく理解していなかったので、今回はそれを釘付けにするために少し努力を払うと思った。私はこれまでのところ、不足している、私の最善の努力は次のとおりです。
find spec -name "*_test.rb" -exec echo mv {} `echo {} | sed s/test/spec/` \;
注意:execの後に余分なエコーがあるため、テスト中にコマンドが実行される代わりに出力されます。
実行すると、一致した各ファイル名の出力は次のとおりです。
mv original original
つまり、sedによる代替が失われました。トリックは何ですか?
これは、sed
が入力として文字列{}
を受け取るために発生します。
find . -exec echo `echo "{}" | sed 's/./foo/g'` \;
これは、ディレクトリ内の各ファイルに対してfoofoo
を再帰的に出力します。この動作の理由は、コマンド全体を展開するときにパイプラインがシェルによって1回実行されるためです。
sed
はシェル経由でコマンドを実行せず、すべてのファイルに対してfind
がパイプラインを実行するような方法でfind
パイプラインを引用する方法はありません。パイプラインまたはバッククォートの概念。 GNU findutilsマニュアルは、パイプラインを別のシェルスクリプトに入れることで同様のタスクを実行する方法を説明しています。
#!/bin/sh
echo "$1" | sed 's/_test.rb$/_spec.rb/'
(sh -c
と大量の引用符を使用して、これらすべてを1つのコマンドで実行するための逆方向の方法があるかもしれませんが、私は試しません。)
元の問題に最も近い方法で解決するには、おそらくxargsの「コマンドラインごとの引数」オプションを使用します。
find . -name *_test.rb | sed -e "p;s/test/spec/" | xargs -n2 mv
現在の作業ディレクトリ内のファイルを再帰的に検索し、元のファイル名(p
)をエコーしてから、変更された名前(s/test/spec/
)をすべてペアでmv
に送ります(xargs -n2
)。この場合、パス自体に文字列test
を含めないでください。
あなたは次のような他の方法を検討したいかもしれません
for file in $(find . -name "*_test.rb")
do
echo mv $file `echo $file | sed s/_test.rb$/_spec.rb/`
done
これはもっと短い
find . -name '*_test.rb' -exec bash -c 'echo mv $0 ${0/test.rb/spec.rb}' {} \;
シェルとしてbash
を使用していると言いますが、この場合、実際にfind
とsed
は必要ありません。
bash
をシェルとして使用していると仮定します。
$ echo $Shell
/bin/bash
$ _
...いわゆるglobstar
シェルオプションを有効にしたと仮定します。
$ shopt -p globstar
shopt -s globstar
$ _
...最後に、rename
ユーティリティ(util-linux-ng
パッケージに含まれています)をインストールしたと仮定します
$ which rename
/usr/bin/rename
$ _
...その後、次のようにbash one-linerでバッチ名を変更できます:
$ rename _test _spec **/*_test.rb
(globstar
シェルオプションは、ディレクトリ階層内のネストの深さに関係なく、bashが一致するすべての*_test.rb
ファイルを検出することを保証します... help shopt
を使用して設定方法を確認しますオプション)
必要に応じて、sedを使用せずに実行できます。
for i in `find -name '*_test.rb'` ; do mv $i ${i%%_test.rb}_spec.rb ; done
${var%%suffix}
は、suffix
の値からvar
を取り除きます。
または、sedを使用して実行するには:
for i in `find -name '*_test.rb'` ; do mv $i `echo $i | sed 's/test/spec/'` ; done
最も簡単な方法:
find . -name "*_test.rb" | xargs rename s/_test/_spec/
最速の方法(4つのプロセッサがあると仮定):
find . -name "*_test.rb" | xargs -P 4 rename s/_test/_spec/
処理するファイルの数が多い場合、xargsにパイプされたファイル名のリストにより、結果のコマンドラインが許可されている最大長を超える可能性があります。
getconf ARG_MAX
を使用してシステムの制限を確認できます
ほとんどのLinuxシステムでは、free -b
またはcat /proc/meminfo
を使用して、作業する必要があるRAMを確認できます。それ以外の場合は、top
またはシステムアクティビティモニターアプリを使用します。
より安全な方法(使用するRAMが1000000バイトあると仮定):
find . -name "*_test.rb" | xargs -s 1000000 rename s/_test/_spec/
このためには、sed
は必要ありません。 process substitution を介してwhile
の結果が渡されるfind
ループで完全に独り立ちできます。
したがって、必要なファイルを選択するfind
式がある場合は、次の構文を使用します。
while IFS= read -r file; do
echo "mv $file ${file%_test.rb}_spec.rb" # remove "echo" when OK!
done < <(find -name "*_test.rb")
これにより、find
ファイルが作成され、すべての名前が文字列_test.rb
の末尾から削除され、_spec.rb
が追加されます。
このステップでは、 Shell Parameter Expansion を使用します。ここで、${var%string}
は、$var
から最短一致パターン「string」を削除します。
$ file="HELLOa_test.rbBYE_test.rb"
$ echo "${file%_test.rb}" # remove _test.rb from the end
HELLOa_test.rbBYE
$ echo "${file%_test.rb}_spec.rb" # remove _test.rb and append _spec.rb
HELLOa_test.rbBYE_spec.rb
例を参照してください。
$ tree
.
├── ab_testArb
├── a_test.rb
├── a_test.rb_test.rb
├── b_test.rb
├── c_test.hello
├── c_test.rb
└── mydir
└── d_test.rb
$ while IFS= read -r file; do echo "mv $file ${file/_test.rb/_spec.rb}"; done < <(find -name "*_test.rb")
mv ./b_test.rb ./b_spec.rb
mv ./mydir/d_test.rb ./mydir/d_spec.rb
mv ./a_test.rb ./a_spec.rb
mv ./c_test.rb ./c_spec.rb
ここに、ファイル名にスペースが含まれていたときに機能したものがあります。以下の例は、すべての.darファイルを再帰的に.Zipファイルに名前変更します。
find . -name "*.dar" -exec bash -c 'mv "$0" "`echo \"$0\" | sed s/.dar/.Zip/`"' {} \;
これは、すべての場合に機能する例です。再帰的に動作し、シェルのみが必要で、スペースを含むファイル名をサポートします。
find spec -name "*_test.rb" -print0 | while read -d $'\0' file; do mv "$file" "`echo $file | sed s/test/spec/`"; done
やり直すつもりはありませんが、 Commandline Find Sed Exec に対する答えでこれを書きました。 1つまたは2つのディレクトリを除外する可能性のあるツリー全体。文字列 "OLD"を含むすべてのファイルおよびディレクトリの名前を、代わりに "NEW"に変更します。
以下の骨の折れる冗長性でhowを記述することに加えて、このメソッドは組み込みのデバッグを組み込むという点でもユニークかもしれません。基本的に、コンパイルされ、要求された作業を実行するために実行する必要があると思われるすべてのコマンドを変数に保存することを除いて、書かれているとおりには何もしません。
また、可能な限り明示的に loops を回避します。 patternの複数の一致に対するsed
再帰検索に加えて、私が知る限り他の再帰はありません。
最後に、これは完全にnull
で区切られています-null
以外のファイル名の文字にはつまずきません。私はあなたがそれを持つべきではないと思います。
ところで、これは[〜#〜] really [〜#〜]高速です。見て:
_% _mvnfind() { mv -n "${1}" "${2}" && cd "${2}"
> read -r SED <<SED
> :;s|${3}\(.*/[^/]*${5}\)|${4}\1|;t;:;s|\(${5}.*\)${3}|\1${4}|;t;s|^[0-9]*[\t]\(mv.*\)${5}|\1|p
> SED
> find . -name "*${3}*" -printf "%d\tmv %P ${5} %P\000" |
> sort -zg | sed -nz ${SED} | read -r ${6}
> echo <<EOF
> Prepared commands saved in variable: ${6}
> To view do: printf ${6} | tr "\000" "\n"
> To run do: sh <<EORUN
> $(printf ${6} | tr "\000" "\n")
> EORUN
> EOF
> }
% rm -rf "${UNNECESSARY:=/any/dirs/you/dont/want/moved}"
% time ( _mvnfind ${SRC=./test_tree} ${TGT=./mv_tree} \
> ${OLD=google} ${NEW=replacement_Word} ${sed_sep=SsEeDd} \
> ${sh_io:=sh_io} ; printf %b\\000 "${sh_io}" | tr "\000" "\n" \
> | wc - ; echo ${sh_io} | tr "\000" "\n" | tail -n 2 )
<actual process time used:>
0.06s user 0.03s system 106% cpu 0.090 total
<output from wc:>
Lines Words Bytes
115 362 20691 -
<output from tail:>
mv .config/replacement_Word-chrome-beta/Default/.../googlestars \
.config/replacement_Word-chrome-beta/Default/.../replacement_wordstars
_
注:上記のfunction
は、_find printf
_を適切に処理するために、GNU
およびsed
のfind
バージョンを必要とする可能性が高いおよび_sed -z -e
_および_:;recursive regex test;t
_呼び出し。これらが利用できない場合、いくつかの小さな調整で機能が複製される可能性があります。
これにより、最初から最後まで必要なすべての操作が非常に簡単に行えます。 fork
でsed
を実行しましたが、いくつかのsed
再帰的分岐テクニックも練習していたので、ここに来ました。理髪師の学校で割引ヘアカットをするようなものです。ワークフローは次のとおりです。
rm -rf ${UNNECESSARY}
_ ./app
_は望ましくない可能性があることに言及しています。事前に削除するか、他の場所に移動するか、または\( -path PATTERN -exec rm -rf \{\} \)
ルーチンをfind
に組み込んでプログラムで実行することもできますが、それはすべてあなた次第です。_mvnfind "${@}"
_ ${sh_io}
_は、関数からの戻り値を保存するという点で特に重要です。 _${sed_sep}
_はすぐに来ます。これは、関数でsed
の再帰を参照するために使用される任意の文字列です。 _${sed_sep}
_が、実行されたパス名またはファイル名のいずれかで潜在的に見つかる可能性のある値に設定されている場合...まあ、それをさせないでください。mv -n $1 $2
_ mv
に設定された_-noclobber
_オプションに注意してください。書かれているように、この関数は_${SRC_DIR}
_が既に存在する場所に_${TGT_DIR}
_を配置しません。read -R SED <<HEREDOC
_ find . -name ${OLD} -printf
_ find
プロセスを開始します。 find
を使用すると、関数の最初のコマンドですべての場所から場所へのmv
操作が既に行われているため、名前の変更が必要なもののみを検索します。たとえば、find
呼び出しのようにexec
を使用して直接アクションを実行するのではなく、代わりに_-printf
_を使用してコマンドラインを動的に構築します。%dir-depth :tab: 'mv '%path-to-${SRC}' '${sed_sep}'%path-again :null delimiter:'
_ find
が必要なファイルを見つけたら、名前の変更を処理するために必要なコマンドを直接ビルドして(most)出力します。各行の先頭に付けられた_%dir-depth
_は、ツリー内のファイルまたはディレクトリの名前を、まだ名前が変更されていない親オブジェクトに変更しようとしていないことを確認するのに役立ちます。 find
は、あらゆる種類の最適化手法を使用してファイルシステムツリーを探索しますが、必要なデータを安全な操作の順序で返すかどうかは不明です。これが次の理由です...sort -general-numerical -zero-delimited
_ find
のすべての出力を_%directory-depth
_に基づいてソートし、$ {SRC}との関係で最も近いパスが最初に機能するようにします。これにより、存在しない場所へのmv
ingファイルに関するエラーを回避でき、再帰ループの必要性を最小限に抑えます。 (実際、ループを見つけるのは難しいかもしれません)sed -ex :rcrs;srch|(save${sep}*til)${OLD}|\saved${SUBSTNEW}|;til ${OLD=0}
%Path
_のみをループします。私が想像した他のすべてのソリューションは、2番目のsed
プロセスを必要とし、短いループは望ましくないかもしれませんが、確かにプロセス全体の生成と分岐を打ち負かします。sed
が行うことは$ {sed_sep}の検索であり、それを見つけたら、$ {OLD}が見つかるまでそれと遭遇したすべての文字を保存し、$ {NEW}に置き換えます。次に、$ {sed_sep}に戻り、文字列内で複数回発生した場合に再び$ {OLD}を探します。見つからない場合は、変更された文字列をstdout
に出力し(次に次にキャッチします)、ループを終了します。mv
コマンド文字列の前半が含まれるようになり、後半は何度も変更されます。 mv
の宛先パスから$ {OLD}名を消去するために必要です。sed -ex...-ex search|%dir_depth(save*)${sed_sep}|(only_saved)|out
-exec
_呼び出しは、2番目のfork
なしで発生します。最初に見てきたように、必要に応じてmv
の_-printf
_関数コマンドによって提供されるfind
コマンドを変更して、$ {OLD}のすべての参照を$ {NEWに適切に変更します}、しかし、そうするために、最終出力に含まれてはならない任意の参照ポイントを使用する必要がありました。したがって、sed
が必要なことをすべて終えたら、参照バッファーを渡す前に参照バッファーを消去するように指示します。そして今すぐ戻ってきました
read
は、次のようなコマンドを受け取ります。
_% mv /path2/$SRC/$OLD_DIR/$OLD_FILE /same/path_w/$NEW_DIR/$NEW_FILE \000
_
read
を_${msg}
_として_${sh_io}
_に変換し、関数の外部で自由に調べることができます。
クール。
-マイク
私が好きなramtamの答えでは、検索部分は正常に機能しますが、パスにスペースが含まれている場合は残りは機能しません。私はsedにあまり精通していませんが、その答えを次のように修正することができました。
find . -name "*_test.rb" | Perl -pe 's/^((.*_)test.rb)$/"\1" "\2spec.rb"/' | xargs -n2 mv
私のユースケースでは最終的なコマンドが次のように見えるため、このような変更が本当に必要でした
find . -name "olddir" | Perl -pe 's/^((.*)olddir)$/"\1" "\2new directory"/' | xargs -n2 mv
例 onitakeが提案することで、スペースを含むファイル名を処理できました。
パスにスペースまたは文字列test
が含まれる場合、これはブレークしません。
find . -name "*_test.rb" -print0 | while read -d $'\0' file
do
echo mv "$file" "$(echo $file | sed s/test/spec/)"
done
Ruby(1.9+)がある場合
Ruby -e 'Dir["**/*._test.rb"].each{|x|test(?f,x) and File.rename(x,x.gsub(/_test/,"_spec") ) }'
トリックを行うニースワンライナーを紹介します。 Sedは、特に-n 2を指定したxargsによって複数の変数が渡される場合、この権利を処理できません。
find ./spec -type f -name "*_test.rb" -print0 | xargs -0 -I {} sh -c 'export file={}; mv $file ${file/_test.rb/_spec.rb}'
-type -fを追加すると、移動操作はファイルのみに制限され、-print 0はパス内の空のスペースを処理します。
$ find spec -name "*_test.rb"
spec/dir2/a_test.rb
spec/dir1/a_test.rb
$ find spec -name "*_test.rb" | xargs -n 1 /usr/bin/Perl -e '($new=$ARGV[0]) =~ s/test/spec/; system(qq(mv),qq(-v), $ARGV[0], $new);'
`spec/dir2/a_test.rb' -> `spec/dir2/a_spec.rb'
`spec/dir1/a_test.rb' -> `spec/dir1/a_spec.rb'
$ find spec -name "*_spec.rb"
spec/dir2/b_spec.rb
spec/dir2/a_spec.rb
spec/dir1/a_spec.rb
spec/dir1/c_spec.rb
Find utilsおよびsed正規表現タイプで名前変更を行うより安全な方法:
mkdir ~/practice
cd ~/practice
touch classic.txt.txt
touch folk.txt.txt
次のように「.txt.txt」拡張子を削除します-
cd ~/practice
find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} \;
;の代わりに+を使用する場合バッチモードで動作するために、上記のコマンドは、最初に一致したファイルのみを名前変更しますが、「find」によって一致するファイルのリスト全体は名前変更しません。
find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} +
あなたの質問はsedに関するもののようですが、再帰的な名前変更の目標を達成するために、私がここで与えた別の答えから恥知らずに引き裂かれた次のものをお勧めします: bashの再帰的な名前変更
#!/bin/bash
IFS=$'\n'
function RecurseDirs
{
for f in "$@"
do
newf=echo "${f}" | sed -e 's/^(.*_)test.rb$/\1spec.rb/g'
echo "${f}" "${newf}"
mv "${f}" "${newf}"
f="${newf}"
if [[ -d "${f}" ]]; then
cd "${f}"
RecurseDirs $(ls -1 ".")
fi
done
cd ..
}
RecurseDirs .
質問に少し関係があるので、この投稿を共有します。詳細を提供しないで申し訳ありません。それが他の誰かを助けることを願っています。 http://www.peteryu.ca/tutorials/shellscripting/batch_rename