【Sed活用講座】文字列中の特定の記号を一括削除する方法


2021/04/04
2021/04/07

Csvデータ全体に渡って、セル内に含まれる文字列に区別無く存在する不要な記号を一括して削除したい場合が有ります。

このような特殊記号は当然ながら手動で検索しながら一つ一つ消していくのも面倒です。また
前回の記事で紹介したようなAwkやJqを利用した行・列のきめ細やかな文字列の置き換えで、Awkでの$0やJqの--slurpで代用してもやれるのですが、入力するテキストデータが巨大になるほど処理速度面で不安が残ります。

ということで今回はCsvファイルの全体に渡るような文字列の置き換えをSedコマンドを利用して、一気に、そして高速に不要な文字を削除する方法を紹介します。


はじめに

当サイトではオフィス業務のComputer-Aidedなハイブリッドな方法を模索し、より効率的なExcel業務を実現したい多忙なオフィスワーカー向けの主にAwkとSedを使うシェル講座です。

シェルスクリプトはどこでもどんなOSでも基本的に使えて、しかも一度使い方を覚えると、Excelと組み合わせて最高に効率の良いオフィスワークツールが作れることでしょう。

合同会社タコスキングダム|蛸壺の技術ブログ


Sedで使う基本的な正規表現

本題に入る前に、Sedの正規表現を少し復習しておきます。

ちなみに本記事で利用するSedは
Gnu sedとします。良く比較されるMacOSXにプリインストールされているBSD sedとは正規表現の扱いが異なる場合があるので注意が必要です。

ところで、Sedでオプション指定できる正規表現には2通りあり、通常スクリプトモード(
-e/--expression)と、Sedスクリプトの中で拡張正規表現が利用できる拡張モード(-r/--regexp-extended)が利用できます。

デフォルトでは通常のスクリプトモードで、拡張モードにするためには明示に
-rを指定しておく必要があります。

ただ通常スクリプトモードで利用する正規表現は以下の表中に表すように非常に紛らわしく、冗長な表現を適宜追加しないと正しく動きません。通常スクリプトモーでは、複雑な文章解析への正規表現を利用するウマ味が薄くなるため、折角正規表現の拡張モードが備わっているならばこちらを積極的に利用する方が良いと思います。

用途

Sed(-rオプション)

sed(-eオプション)

文字エスケープ

\

\

任意の1文字

.

.

数字1文字

\d か [0-9]

\d か [0-9]

数字以外の1文字

\D か [^0-9]

\D か [^0-9]

アルファベットか数字かアンダーバーの1文字

\w か [a-zA-Z_0-9]

\w か [a-zA-Z_0-9]

アルファベットか数字かアンダーバー以外の1文字

\W か [^a-zA-Z_0-9]

\W か [^a-zA-Z_0-9]

空白文字

\s か [ \n\r\f\t]

\s か [ \n\r\f\t]

空白文字以外

\S か [^ \n\r\f\t]

\S か [^ \n\r\f\t]

パターンどれかにマッチ

パターンA|パターンB|パターンC

パターンA\|パターンB\|パターンC

グループ化

(パターン)

\(パターン\)

...とグループ化でキャプチャした値の取得

\1

\1

パターンが先頭

^パターン

^パターン

パターンが末尾

パターン$

パターン$

パターンが0または1回

パターン?

パターン\?

パターンが0回以上

パターン*

パターン*

パターンが1回以上

パターン+

パターン\+

パターンがm回

パターン{m}

パターン\{m\}

パターンがm回以上

パターン{m,}

パターン\{m,\}

パターンがn回以下

パターン{,n}

パターン\{,n\}

パターンがm回以上、n回以下

パターン{m,n}

パターン\{m,n\}

このように、通常スクリプトモードと、拡張モードとは、正規表現の用法が異なるものがいくつかありますので、コーディングの際には注意が必要です。

たとえば、Sedの通常スクリプトモードには中括弧や丸括弧には文字エスケープが必要ですが、釘括弧はそうではないようです。

また+や?などの文字にも文字エスケープが必要ですが、,や*などには気を使う必要はない...などなど、Sedで正規表現を使うときに色々と気を遣うよりは、拡張モードを使う方が良いでしょう。

ちなみにGnu sedでは以下のPOSIXキャラクターが利用できます。こちらも覚えておくと特定の文字をマッチさせたい場合に便利なときがあります。

呼び出し

定義

[:alpha:]

アルファベット

[:alnum:]

アルファベットと数字

[:digit:]

数字

[:xdigit:]

数字(十六進数も含む)

[:lower:]

アルファベットの小文字

[:upper:]

アルファベットの大文字

[:blank:]

空白文字(スペース/タブ等)

[:graph:]

非空白文字(表示可能文字)

[:cntrl:]

制御文字

[:print:]

表示可能文字(制御文字以外)

[:space:]

スペース/タブ/改行文字

[:punct:]

句読点(通常文字/数字/制御文字/スペース文字以外)

ちなみにPOSIXキャラクターを使ったSedの用法は以下のようになります。

            
            #👇スペースを全部消去
$ echo 'a b c d e f g' | sed -r 's/[[:space:]]//g'
abcdefg

#👇16進数(0-9a-fA-F)を全部消去
$ echo 'HEX: 01 2e 24 a5 8c 2f de' | sed -r 's/[[:xdigit:]]//g'
HX:
        

実践例① ~ 囲い文字の全消去

それでは具体的な例をやってみます。CSV形式のデータを扱う時にもっとも良く使うだろう例としては"のようなクォーテーション記号を全て排除したいケースを考えてみましょう。

            
            $ sed -r 's/"//g' <<EOF
"aaa"
"100","9"
"1 2 3 4 5",,,41,"冷麦"
EOF
#👇出力
aaa
100,9
1 2 3 4 5,,,41,冷麦
        
また"(ダブルクオーテーション)文字だけなく'(シングルクォーテーション)文字も同時に置き換えしたい場合には以下のようにします。

            
            $ sed -r "s/\"|'//g" << EOF
"aaa",'bbb'
"100",'rgb',"9"
"1 2 3 4 5",'3','8',41,"冷麦"
EOF
#👇出力
aaa,bbb
100,rgb,9
1 2 3 4 5,3,8,41,冷麦
        
もしくは

            
            $ sed -r "s/[\"']//g" << EOF
"aaa",'bbb'
"100",'rgb',"9"
"1 2 3 4 5",'3','8',41,"冷麦"
EOF
#👇出力
aaa,bbb
100,rgb,9
1 2 3 4 5,3,8,41,冷麦
        
とかけます。

ちなみにこのスクリプトの場合、シングルクォーテーション文字でも置き換えがしたいので、sedのプログラム構文ではダブルクォーテーション
"で囲って、その中で\"'いうように2つを文字として認識させているようにしています。

どうしてもsedのプログラム部分はシングルクォーテーション文字(')で囲いたい方は、
'...'スコープ外'....'というようにシングルクォーテーションを分割することで特殊文字を外部で一時的に利用するテクニックが利用できます。

            
            $ sed -r 's/"|'"'"'//g' << EOF
"aaa",'bbb'
"100",'rgb',"9"
"1 2 3 4 5",'3','8',41,"冷麦"
EOF
#👇出力
aaa,bbb
100,rgb,9
1 2 3 4 5,3,8,41,冷麦
EOF
        
ここでは's/"|'"'"'//g'というように3つのパートに分割しているような感覚で使っています。スクリプトの部分が少し見辛くなることが難点ですが、いざというときには使えるテクニックかもしれません。

デリミタを変える

Sedでは伝統的に/(スラッシュ記号)をデリミタ文字として使って置き換え前後のパターンを表現することが多いですが、実際は/無くても任意の1文字であれば正常に動作します。

従って
/を削除したい文字の対象になっている場合には、/以外の最適な文字をデリミタにしなければいけません。

            
            $ sed -r 's@"@@g' <<EOF
"aaa"
"100","9"
"1 2 3 4 5",,,41,"冷麦"
EOF
aaa
100,9
1 2 3 4 5,,,41,冷麦
        

実践例② ~ 全角の数字やアルファベットを半角へ一括変換

こちらもCSVデータをAwkやJqで数値を集計計算する上で、前処理としてやっておくと便利なテクニックです。

たまに半角と全角の数値が混合して出現するExcelデータ内の数値を全て半角にする際のスクリプトをSedコマンドで変換したい場合には、yコマンド利用できます。

yコマンドは、
y/置換前文字リスト/置換後文字リスト/という書式で、パターンスペースにある文字列から置換前リストから文字を検索し、置換後リストの同じインデックス位置にある文字に置換することができます。

            
            $ echo 'ABcdefg0123456789' | sed '
y/0123456789/0123456789/;
y/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/;
y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
'
ABcdefg0123456789
        
ほとんどのLinuxOSでは上記のやり方でOKなのですが、一部のAlpineなどのような軽量のディストーションでは、locateなどのマルチバイト文字を取り扱うライブラリがインストールされていないことがあるので、LANG環境変数が設定されず、マルチバイト文字対応になっていない場合があります。

このyコマンドの性質上、文字の置き換え位置がバイト数が揃っていないと以下のように正しく動作しません。

            
            #👇マルチバイト文字未対応の環境
$ echo 'ABcdefg0123456789' | sed '
y/0123456789/0123456789/;
y/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/;
y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
'
A01Fcd0bo0brg012101801�401�6701�01�
        
この場合、OS環境に則したマルチバイトのライブラリをインストールするか、以下のように泥臭い作業ですが、sコマンドで一文字づつ置き換えパターンを作成すると、OS環境に依らない置き換えも可能です。

            
            echo 'ABcdefg0123456789' | sed -e '
s/0/0/g;s/1/1/g;s/2/2/g;s/3/3/g;s/4/4/g;s/5/5/g;s/6/6/g;s/7/7/g;s/8/8/g;s/9/9/g;
s/a/a/g;s/b/b/g;s/c/c/g;s/d/d/g;s/e/e/g;s/f/f/g;s/g/g/g;s/h/h/g;s/i/i/g;s/j/j/g;s/k/k/g;s/l/l/g;s/m/m/g;s/n/n/g;s/o/o/g;s/p/p/g;s/q/q/g;s/r/r/g;s/s/s/g;s/t/t/g;s/u/u/g;s/v/v/g;s/w/w/g;s/x/x/g;s/y/y/g;s/z/z/g;
s/A/A/g;s/B/B/g;s/C/C/g;s/D/D/g;s/E/E/g;s/F/F/g;s/G/G/g;s/H/H/g;s/I/I/g;s/J/J/g;s/K/K/g;s/L/L/g;s/M/M/g;s/N/N/g;s/O/O/g;s/P/P/g;s/Q/Q/g;s/R/R/g;s/S/S/g;s/T/T/g;s/U/U/g;s/V/V/g;s/W/W/g;s/X/X/g;s/Y/Y/g;s/Z/Z/g;
'
ABcdefg0123456789
        

まとめ

以上、Sedによる正規表現を用いたテキストファイルの文字列の置き換えの話を簡単にまとめてみました。
記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。