※追記:②を記事化しました。①のあとご興味があればご参照ください。

■BASHとPowerShellのフィルタ処理まとめ②

【フィルタとは】

フィルタとはコマンドの一種で、標準入力からデータを受け取ってそれに変更を加えて標準出力に書き出すという働きをするものです。データの受け渡しには通常パイプラインを利用します。これを複数経由させることでかなり複雑なデータの変換処理を行うことも可能です。

パイプラインを利用するときは | という記号でコマンドをつなぎます。これはBASHもPowerShellも同じです。

BASH
cat /etc/passwd | cut -d: -f 1,3,4,7 | tail -3
(最近登録されたユーザID3つ分のID/ユーザID/グループID/ホームディレクトリを取り出すコマンド処理)
PowerShell
Get-EventLog Application | foreach {"{0} {1} {2}" -F $_.Source,$_.InstanceID,$_.EntryType} | Select-Object -First 3
(最新三つのアプリケーションログからソース、イベントID、深刻度の順番で出力)

このように書くことで、一番左側のコマンドをフィルタ役のコマンドに渡すことで任意のフィルタ処理を加え続けることができます。上記はBASHもPowerShellも「任意の列を好みの順番で切り出し」、「任意の行数のみ出力」する処理をパイプで渡しながら行っています。

ここ何か月かBASHとPowerShellを勉強しており、二つともパイプでフィルタ処理を掛ける部分がよく似ているので、比較しながらまとめてみたくなりました。「文字列置換する場合」や「空行削除の場合」などで章ごとに分けてまとめ、お互いどこが不得意でどこが得意か確認していくのが狙いです。

【検証環境とデータについて】

検証環境は以下の通り。

 ・CentOS 7.4(シェルはBASH)
 ・Windows10端末
 ・PowerShellバージョン↓

PowerShellVersion
PS C:\Tools\logs> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.16299.251
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.16299.251
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

テスト用のフィルタするファイルは共通して以下3つを使用します。

personal_infomation01.csv
***<空行>***
***<空行>***
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
1,海野華音,ウミノカノン,女,1991/06/20,26,静岡県,AB
2,有馬花菜,アリマハナ,女,1989/12/25,28,栃木県,A
3,小柳有紀,コヤナギユキ,女,1998/04/04,20,福井県,O
4,米沢里桜,ヨネザワリオ,女,1989/05/05,28,長崎県,A
5,楠本綾菜,クスモトアヤナ,女,1979/07/04,38,静岡県,O
***<空行>***
***<空行>***
***<空行>***
personal_infomation02.csv
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
1,島田清蔵,シマダセイゾウ,男,1982/08/25,35,鳥取県,A
2,河上里穂,カワカミリホ,女,1969/09/21,48,高知県,B
3,三輪博昭,ミワヒロアキ,男,1975/05/25,42,福岡県,O
4,本郷勝次,ホンゴウカツジ,男,1966/02/17,52,東京都,O
5,横川金次,ヨコカワキンジ,男,1985/01/14,33,徳島県,AB
personal_infomation03.csv
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,A
2,川名来未,カワナクルミ,女,1976/05/15,41,大分県,O
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A

以下のサイトで作成しました。
■疑似個人情報データ生成サービス

では以下よりぽんぽんとフィルタ紹介と比較をしていきます。

【文字列置換】

ではそれぞれで"A"型を"B"型に置換します。

BASH文字列置換01
[root@centos74 log]# cat personal_infomation03.csv | sed -e 's/A/B/'
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,B
2,川名来未,カワナクルミ,女,1976/05/15,41,大分県,O
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,B
PowerShell文字列置換01
PS C:\Tools\logs> (Get-Content .\personal_infomation03.csv).Replace("A","B")
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,B
2,川名来未,カワナクルミ,女,1976/05/15,41,大分県,O
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,B

PowerShellはパイプではなくReplaceメソッドで行っています。

今回、章が増えそうなのでこんな感じでサクッと基本コマンドだけ提示します。
 ※各々の詳細はまた別記事にでもまとめます。

【文字列の削除】

"A"を消します。

BASH文字列置換01
[root@centos74 log]# cat personal_infomation03.csv | sed -e 's/A//g'
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,
2,川名来未,カワナクルミ,女,1976/05/15,41,大分県,O
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,
PowerShell文字列置換01
PS C:\Tools\logs> (Get-Content .\personal_infomation03.csv).Replace("A","")
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,
2,川名来未,カワナクルミ,女,1976/05/15,41,大分県,O
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,

置換と同じですね。

【行頭の文字列を消す】

行頭の早川さん手前の"1"だけ消します。

BASH行頭文字列削除01
[root@centos74 log]# cat personal_infomation03.csv | sed -e 's/^1//'
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,A
2,川名来未,カワナクルミ,女,1976/05/15,41,大分県,O
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A

BASHでは^は「行の最初」を表します。この場合「先頭文字が1」を指定しています。
^がないと他の1も一気に消してしまうので注意してください。(生年月日がすごいことになります)

PowerShell行頭文字列削除01
PS C:\Tools\logs> Get-Content .\personal_infomation03.csv | foreach { $_ -replace "^1",""}
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,A
2,川名来未,カワナクルミ,女,1976/05/15,41,大分県,O
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A

PowerShellも同じく^を使えます。ただ先ほどのReplaceメソッドだと正規表現がつかえず消すことはできないので注意が必要です。foreachは一行一行に対して{}で括ったコマンドを実行できるループ処理です。BASHだと「while read LINE~do~done<<標準入力」に相当します。便利です。

PowerShell行頭文字列削除02(消せないパターン)
PS C:\Tools\logs> (Get-Content .\personal_infomation03.csv).Replace("^1","")
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,A
2,川名来未,カワナクルミ,女,1976/05/15,41,大分県,O
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A

【行末の文字列を消す】

行末の"大分県,O"を消します。

BASH行末文字列削除01
[root@centos74 log]# cat personal_infomation03.csv | sed -e 's/大分県,O$//'
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,A
2,川名来未,カワナクルミ,女,1976/05/15,41,
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A

行頭が^であったのに対して行末を意味する文字は$です。

PowerShell行末文字列削除01
PS C:\Tools\logs> Get-Content .\personal_infomation03.csv | foreach { $_ -replace "大分県,O$",""}
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,A
2,川名来未,カワナクルミ,女,1976/05/15,41,
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A

PowerShellでも行末の意味として使えます。Replaceメソッドでは正規表現が使えない点は$も同じなので省略します。

【文字列を追加する】

"男"に"性"をつけて"男性"とします。
これは厳密には追加ではなく置換ですが、結果が文字列追加になります。

BASH文字列追加01
[root@centos74 log]# cat personal_infomation03.csv | sed -e 's/男/男性/'
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
1,早川哲二,ハヤカワテツジ,男性,1996/07/11,21,愛知県,A
2,川名来未,カワナクルミ,女,1976/05/15,41,大分県,O
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A
PowerShell文字列追加01
PS C:\Tools\logs> (Get-Content .\personal_infomation03.csv).Replace("男","男性")
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
1,早川哲二,ハヤカワテツジ,男性,1996/07/11,21,愛知県,A
2,川名来未,カワナクルミ,女,1976/05/15,41,大分県,O
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A

よく似てますよね。もう勘づいている方もいると思いますが、BASHのsedによる文字列処理に関しては、PowerShellのReplaceでほぼ同じ文体で処理できてしまいます。ぽんぽんいきます。

【行の先頭に文字列を追加する】

^は行の先頭なので、その部分に文字列を入れ込むには次のように書きます。
"Japan,"を各行の先頭に入れ込みます。

BASH先頭に文字列追加01
[root@centos74 log]# cat personal_infomation03.csv | sed -e 's/^/Japan,/'
Japan,連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
Japan,1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,A
Japan,2,川名来未,カワナクルミ,女,1976/05/15,41,大分県,O
Japan,3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A
PowerShell先頭に文字列追加01
PS C:\Tools\logs> Get-Content .\personal_infomation03.csv | foreach { $_ -replace "^","Japan," }
Japan,連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
Japan,1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,A
Japan,2,川名来未,カワナクルミ,女,1976/05/15,41,大分県,O
Japan,3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A

【行末に文字列を追加する】

行末は$で表現します。以下の書き方になります。
",野球"を各行に追加します。

BASH行末に文字列追加01
[root@centos74 log]# cat personal_infomation03.csv | sed -e 's/$/,野球/'
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型,野球
1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,A,野球
2,川名来未,カワナクルミ,女,1976/05/15,41,大分県,O,野球
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A,野球
PowerShell行末に文字列追加01
PS C:\Tools\logs> Get-Content .\personal_infomation03.csv | foreach { $_ -replace "$",",野球" }
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型,野球
1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,A,野球
2,川名来未,カワナクルミ,女,1976/05/15,41,大分県,O,野球
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A,野球

【ドット(.)とアスタリスク(*)】

これまで決まった文字列に対して置換、追加、削除などを行ってきましたが、今度は「任意の文字列」に対する処理を紹介し、二つを比較します。例えばaで始まってxで終わる文字列とか、aがあったらそれ以降全部という指定ができるということです。

ここでは"."と"*"という二つの特殊文字を紹介します。

"."には「任意の1文字」の意味があります。"."はあらゆる文字を代替して表現できます。今回は「先頭の"2"より三文字だけ消す」という処理をそれぞれで行います。以下の通りです。

BASH任意の文字数だけ文字列削除01
[root@centos74 log]# cat personal_infomation03.csv | sed -e 's/^2...//'
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,A
来未,カワナクルミ,女,1976/05/15,41,大分県,O
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A
PowerShell任意の文字数だけ文字列削除01
PS C:\Tools\logs> Get-Content .\personal_infomation03.csv | foreach { $_ -replace "^2...","" }
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,A
来未,カワナクルミ,女,1976/05/15,41,大分県,O
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A

" * " は「直前の文字が任意の個数連続した場合(0個も含む)」を表します。"a*"という指定は、"a"や"aaa"、"aaaaaa"など、aが任意の個数続く文字列を表します。

ここから大事なことですが、この二つを組み合わせて".* "と表記すると、「任意の文字列」を表します。どんな文字列でも.*と表現することができるようになります。ここで紹介した中で今のところ一番便利な書き方なので覚えてもらった方がいいと思います。以下は「"先頭に"2"が含まれる文字列を削除」します。

BASH任意の文字列を該当するもの全て削除01
[root@centos74 log]# cat personal_infomation03.csv | sed -e 's/^2.*//'
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,A

3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A
PowerShell任意の文字列を該当するもの全て削除01
PS C:\Tools\logs> Get-Content .\personal_infomation03.csv | foreach { $_ -replace "^2.*","" }
連番,氏名,氏名(カタカナ),性別,生年月日,年齢,出身地,血液型
1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,A

3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A

結果的に行(の文字列)が削除され空行となりました。
(また別記事で紹介予定の)空行消しと合わせると複数ファイルをマージする時に便利です。
書き方はこうです。

BASH(先頭に"連番"がある文字列を削除後、空行を削除)
[root@centos74 log]# cat personal_infomation*.csv | sed -e 's/^連番.*//' | sed '/^$/d'
1,海野華音,ウミノカノン,女,1991/06/20,26,静岡県,AB
2,有馬花菜,アリマハナ,女,1989/12/25,28,栃木県,A
3,小柳有紀,コヤナギユキ,女,1998/04/04,20,福井県,O
4,米沢里桜,ヨネザワリオ,女,1989/05/05,28,長崎県,A
5,楠本綾菜,クスモトアヤナ,女,1979/07/04,38,静岡県,O
1,島田清蔵,シマダセイゾウ,男,1982/08/25,35,鳥取県,A
2,河上里穂,カワカミリホ,女,1969/09/21,48,高知県,B
3,三輪博昭,ミワヒロアキ,男,1975/05/25,42,福岡県,O
4,本郷勝次,ホンゴウカツジ,男,1966/02/17,52,東京都,O
5,横川金次,ヨコカワキンジ,男,1985/01/14,33,徳島県,AB
1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,A
2,川名来未,カワナクルミ,女,1976/05/15,41,大分県,O
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A
PowerShell(先頭に"連番"がある文字列を削除後、空行を削除)
PS C:\Tools\logs> Get-Content .\personal_infomation*.csv | foreach { $_ -replace "^連番.*","" } | Where-Object { $_.trim() -ne "" }
1,海野華音,ウミノカノン,女,1991/06/20,26,静岡県,AB
2,有馬花菜,アリマハナ,女,1989/12/25,28,栃木県,A
3,小柳有紀,コヤナギユキ,女,1998/04/04,20,福井県,O
4,米沢里桜,ヨネザワリオ,女,1989/05/05,28,長崎県,A
5,楠本綾菜,クスモトアヤナ,女,1979/07/04,38,静岡県,O
1,島田清蔵,シマダセイゾウ,男,1982/08/25,35,鳥取県,A
2,河上里穂,カワカミリホ,女,1969/09/21,48,高知県,B
3,三輪博昭,ミワヒロアキ,男,1975/05/25,42,福岡県,O
4,本郷勝次,ホンゴウカツジ,男,1966/02/17,52,東京都,O
5,横川金次,ヨコカワキンジ,男,1985/01/14,33,徳島県,AB
1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,A
2,川名来未,カワナクルミ,女,1976/05/15,41,大分県,O
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A
PS C:\Tools\logs>

ここからさらに「先頭が"3"で血液型"O"型の方の行」を指定して削除します。

BASH(先頭に"連番"文字列と先頭に"3"かつ行末に"O"がある文字列を削除後、空行を削除)
[root@centos74 log]# cat personal_infomation*.csv | sed -e 's/^連番.*//' -e 's/^3.*O$//g' | sed '/^$/d'
1,海野華音,ウミノカノン,女,1991/06/20,26,静岡県,AB
2,有馬花菜,アリマハナ,女,1989/12/25,28,栃木県,A
4,米沢里桜,ヨネザワリオ,女,1989/05/05,28,長崎県,A
5,楠本綾菜,クスモトアヤナ,女,1979/07/04,38,静岡県,O
1,島田清蔵,シマダセイゾウ,男,1982/08/25,35,鳥取県,A
2,河上里穂,カワカミリホ,女,1969/09/21,48,高知県,B
4,本郷勝次,ホンゴウカツジ,男,1966/02/17,52,東京都,O
5,横川金次,ヨコカワキンジ,男,1985/01/14,33,徳島県,AB
1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,A
2,川名来未,カワナクルミ,女,1976/05/15,41,大分県,O
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A
PowerShell(先頭に"連番"文字列と先頭に"3"かつ行末に"O"がある文字列を削除後、空行を削除)
PS C:\Tools\logs> Get-Content .\personal_infomation*.csv | foreach { $_ -replace "^連番.*","" } | foreach { $_ -replace "^3.*O$",""} | Where-Object { $_.trim() -ne "" }
1,海野華音,ウミノカノン,女,1991/06/20,26,静岡県,AB
2,有馬花菜,アリマハナ,女,1989/12/25,28,栃木県,A
4,米沢里桜,ヨネザワリオ,女,1989/05/05,28,長崎県,A
5,楠本綾菜,クスモトアヤナ,女,1979/07/04,38,静岡県,O
1,島田清蔵,シマダセイゾウ,男,1982/08/25,35,鳥取県,A
2,河上里穂,カワカミリホ,女,1969/09/21,48,高知県,B
4,本郷勝次,ホンゴウカツジ,男,1966/02/17,52,東京都,O
5,横川金次,ヨコカワキンジ,男,1985/01/14,33,徳島県,AB
1,早川哲二,ハヤカワテツジ,男,1996/07/11,21,愛知県,A
2,川名来未,カワナクルミ,女,1976/05/15,41,大分県,O
3,村田蒼衣,ムラタアオイ,女,1967/03/27,51,奈良県,A

【まとめと今後の予定】

ここまでの文字列操作ではあまりBASHとPowerShell変らないなあと思っていましたが、sedとforeachでの文字列指定を複数行う場合はBASHが簡素で良いですね。パイプせずに-eを続ければオッケーです。いや、でもPowerShellもうまい書き方がありそうな気が...。もし、知ってらっしゃる方がいたら教えてください。。

長くなってきたので一旦これで記事を区切ります。またちょいちょいとまとめていきます。今のところはパイプで渡していく構文や正規表現が使えたりとよく似ていますが、本質的にはBASHはテキスト処理、PowerShellはオブジェクト処理なので得手不得手の部分が見えてくるはずです。

また今回のこの記事をお読み頂いて「こういう方法もあるよ」とか「こうするともっと効率良いよ」というご指摘を頂けたら大変ありがたいです。遠慮なくコメントください。

次の②の記事では以下のテーマを比較する予定です。...予定です。

 ・大文字と小文字を入れ替える
 ・タブをスペースに変換
 ・複数のスペースを1個のスペースに変換
 ・ホワイトスペースを1個のスペースに変換
 ・行頭のホワイトスペースを削除
 ・行末のホワイトスペースを削除

その次の③は以下(の予定)。ここらへんはBASHの方が強そう打と思っています。

 ・文字列指定による行の削除
 ・空白行の削除
 ・行の削除
 ・指定した行の表示
 ・コメント行の削除
 ・キーワードによる行の指定
 ・ファイルを後ろから表示

④は具体的に決めてませんが計算系(四則演算や合計、集計、平均値算出)などを取り上げたいです。フィルタではありませんが、ここらへんはPowerShellが強いかもしれないのでまとめたいなと。特に小数計算のところとか型変換できますからね。

以上です。
長文の記事をお読み頂きありがとうございました。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.