ファイルから複数の特殊文字を削除するにはどうすればよいですか?

ファイルから複数の特殊文字を削除するにはどうすればよいですか?

以下のスクリプトは現在、^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

オプションを使用すると-dtr指定された文字が削除されます。削除する文字は、最初の非オプション引数として一緒に渡されます。バックスラッシュ エスケープを使用して、\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

関連情報