findとawkを使用してtcshシェルスクリプトでファイル名の感嘆符を参照する方法は?

findとawkを使用してtcshシェルスクリプトでファイル名の感嘆符を参照する方法は?

ファイル名でスペース文字 " "、感嘆符 " !"、およびドル記号 " $"を検索し、各文字を下線 " _"に置き換えるスクリプトがあります。ただし、感嘆符付きのファイル名はまったく処理されません。現在のフォルダとすべてのサブフォルダから名前を変更するファイルを特定の深さまで検索します(ここではあまり関係ありません)。フォルダ名も同じように変更する必要があり、これがディープインターリーブが機能する理由です。

必須の名前変更スキームは次のとおりです。

This is cool!.txt     -->    This_is_cool_.txt
Thank$.log            -->    Thank_.log
Foo 1/Bar 2/a.txt     -->    Foo_1/Bar_2/a.txt

スクリプトは次のとおりです。

#!/bin/tcsh -f
  
foreach n ( 1 2 3 4 5 6 7 8 )
    find . -mindepth $n -maxdepth $n -name '*[ $\!]*' | fgrep -v \" | \
    awk '{printf "mv -i -- \"%s\" \"%s\"\n", gensub("!","\\\\\!","g",$0), gensub(" ","_","g",gensub("\\$","\\\\$","g",gensub("!","\\\\\!","g",$0)))}' | tcsh -cf
end 

\2つの感嘆符の前のバックスラッシュの数を変更しましたが、効果はありません。エラーメッセージは次のとおりです(バックスラッシュの数が偶数)。

awk: cmd. line:1: warning: escape sequence `\!' treated as plain `!'

それとも(明らかに)対話型シェルを開くシェルスクリプトですか? !時には何も起こりません(mv最初の引数にバックスラッシュが多すぎてファイルが見つからない場合のようです)。

PS:見てわかるように、"二重引用符 ""を含むファイルも機能できないため除外しました。これらの問題を解決する方法も提案してください。

答え1

mvコマンドを合成してawkシェルにパイプするよりも直接呼び出す方が簡単です。

#!/bin/bash
#
find -mindepth 1 -maxdepth 8 -type f -name '*[ !$]*' -execdir
    bash -c 'for f in "$@"; do echo mv -- "$f" "${f//[ !$]/_}"; done' _ {} +

このバージョンはファイル名を変更しますが、ディレクトリ名は変更しません(質問に記載されているように)。実際にディレクトリ名を変更するには、このバリアントを使用してください。

find -mindepth 1 -maxdepth 8 -depth -name '*[ !$]*' -execdir
    bash -c 'for f in "$@"; do echo mv -- "$f" "${f//[ !$]/_}"; done' _ {} +

どちらの場合も、echoコマンドが期待どおりに実行されることに満足している場合は、コマンドを削除してください。対話型の名前変更に使用されますmv -i。下線はbash -c '...' _ {}サブシェルで実行されるコードの「名前」になります。これはマッピングされ、$0コードエラー(存在する場合)を報告するために使用されるため、bashそこに配置できます。ただsubshellラベルだけです。

答え2

このスクリプトは深さを制限せずにすべてのfind文字$!"'_

質問には特定のバージョンのオペレーティングシステムやツールに関する情報が含まれていないため、POSIX仕様にのみ依存しており、GNUなどの特定のバージョンは要求しないように努めました。

#!/bin/sh
find . -depth -name '*[ $!"]*' | while IFS= read -r file
do
   dir="${file%/*}"
   newname="$(echo "${file##*/}" | tr ' $!"' '____')"
   echo mv "$file" "$dir/$newname"
done

このスクリプトは軽くテストされました。正しいmvコマンドが印刷されたら削除してくださいecho

説明する:

find . -depth...ファイルがディレクトリの前に印刷されていることを確認してください(両方とも置換する文字が含まれている場合)。このようにして、最初にファイル名を変更し、次に親ディレクトリの名前を変更します。

``で区切られた親ディレクトリ名はdir="${file%/*}変更されません。

ファイル名(または最後のディレクトリ名)が抽出されましたnewname="$(echo "${file##*/}" | tr ' $!"' '____')"


さらなる改善

ファイル名の他の「奇妙な」文字を処理するには、次のようにタスクからfind直接ループ本文のコードを呼び出すことをお勧めします。-execロエマ~の回答

find . -depth -name '*[ $!"]*' -exec sh -c 'for file in "$@" ; do dir="${file%/*}" ; newname="$(echo "${file##*/}" | tr '\'' $!"'\'' "____")" ; echo mv "$file" "$dir/$newname"; done' _ {} +

また、見ることができますhttps://mywiki.wooledge.org/BashFAQ/020

他の回答でasを使用すると、-execdirファイル名と親ディレクトリが分離されるのを防ぐことができますが、POSIXはこれを定義しません。

echo...組み合わせなどのシェルと交換できます。|tr"${newname//[ $!\"]/_}"

関連情報