以下のスクリプトは現在、^M 文字 ( Ctrl+V+M
) を削除します。少し長い気がしますが、^I や将来的に使用する可能性のある他の文字も追加する必要があります。
^I ( ) を追加するより簡単な方法はありますかCtrl+V+I
? これは、2 日間のシェル プログラミング クラスに参加した後、約 6 か月前に自分で書いた最初のスクリプトです。必要以上に長くしすぎたかどうかはわかりませんが、一般的なヒントがあれば教えていただけるとありがたいです。
#!/bin/bash
echo "$# item(s) to review."
question='Do you want to remove the ^M characters?'
for file
do
if grep "^M" "$file" >> /dev/null 2> /dev/null
then
echo "$file contains special characters"
echo $question
read answer
if [[ "$answer" == [yY] ]]
then
cat "$file" | sed "s/^M//" > "$file.safe"
echo "Special characters have been removed and $file.safe has been created."
elif [[ "$answer" == [yY][eE][sSaA]* ]]
then
cat "$file" | sed "s/^M//" > "$file.safe"
echo "Special characters have been removed and $file.safe has been created."
else
echo "Special characters have NOT been removed."
fi
elif [[ -d $file ]]
then
echo "$file is a directory"
else
echo "No special characters in $file"
fi
done
答え1
これは確かに必要以上に長いです。必要なのはtr
ユーティリティ、さらにスクリプトに引数として渡されるファイルに対して動作するためのループとリダイレクトもあります。
#!/bin/sh
for file do
tr -d '\r\t' <"$file" >"$file.safe"
done
オプションを使用すると-d
、tr
指定された文字が削除されます。削除する文字は、最初の非オプション引数として一緒に渡されます。バックスラッシュ エスケープを使用して、\n
改行 (^J)、\r
キャリッジ リターン (^M)、\t
タブ (^I) などの特殊文字を表すことができます。
ユーザーに問い合わせるコードは無意味なので再現していません。ディレクトリはリダイレクトでエラーを引き起こしますし、ディレクトリを通常のファイルとして扱うなどの無意味なアクションを要求しないのは呼び出し側の仕事なので、その部分も省略しました。
元のファイルを置き換える場合は、一時ファイルに書き込んでから、結果をその場所に移動します。
#!/bin/sh
for file do
tmp="$(TMPDIR=$(dirname -- "$file") mktemp)"
tr -d '\r\t' <"$file" >"$tmp" && mv -f -- "$tmp" "$file"
done
一時ファイル名は、mktemp
スクリプトが堅牢になるように を使用して作成されます。ファイルを含むディレクトリへの書き込み権限がある限り、既存のファイルを上書きするリスクなしに機能します。そのディレクトリが他のユーザーによって書き込み可能で、他のユーザーが他のデータを挿入しようとする場合でも安全です ( の潜在的な問題/tmp
)。
このmv
コマンドは呼び出しが成功した場合にのみ呼び出されるため、途中でディスクがいっぱいになるなどして失敗したtr
場合でもデータが失われるリスクはありません。tr
ファイルに特殊文字が含まれていない場合に、そのファイルを新しい同一ファイルに置き換えないようにするには、次の 2 つの方法があります。
まず特殊文字をチェックします。これにはいくつかの方法があります。1 つの方法は、特殊文字以外のすべてを削除し、結果として生じる文字の数を数えることです。最適化のために、パイプ処理を行い、
head -c 1
特殊文字が先頭近くに見つかった場合にファイル全体を調べる必要がないようにします。こうすることで、何もしない場合はカウントが 0 になり、それ以外の場合は 1 になります。if [ "$(tr -dc '\r\t' <"$file" | head -c 1 | wc -c)" -ne 0 ]; then tr -d '\r\t' <"$file" >"$tmp" && mv -f -- "$tmp" "$file" fi
変換してから、元のファイルと同一かどうかを確認できます。ファイルがすでに目的の状態になっていることが多い場合は、この方法では遅くなる可能性があります。一方、この手法は、ファイルが目的の状態にあるかどうかを判断するのが容易でない場合にも適用できます。
tr -d '\r\t' <"$file" >"$tmp" && if cmp -s "$tmp" "$file"; then rm -- "$tmp" else mv -f -- "$tmp" "$file" fi
答え2
スクリプトの周りにループを配置することができます。つまり、次のようになります。
for c in "^I" "^M"; do
for file; do
if grep "$c" "$file"; then
...
etc.
...
fi
done
done
答え3
私はこの Perl ワンライナーを好みます。'\cM' はコントロール M 文字です。元のファイルは拡張子 '.bak' でバックアップされます。この拡張子は選択できます。
perl -i.bak -pe 's/\cM//g;' file(s)
削除する文字のクラスを使用する例。括弧内では、perl は control-I と control-M を見つけて削除します。ただし、私はこれを正確にテストしていません。
perl -i.bak -pe 's/[\cM\cI]//g;' files(s)
答え4
使用を検討したことがありますか
tr -d .....<characterlist>....
たとえば、印刷できない文字を削除して別のファイルに格納します。
cat filename | tr -cd '[:print:]' >/tmp/x.out
アプリケーションに合わせて文字リストを変更します。tr
詳細については、man ページを参照してください。
また、正規表現の範囲が許可されているので便利です。
echo '\001\002\003\004' | tr -d '[\001-\003]' | od -c