初歩のシェルスクリプトで遊ぶ[シェルスクリプトの注意点はどこだろう(3)]

sedは「while read ; do ... ;done」と似ている

【 sed 】コマンド(基礎編)――テキストファイルを編集する:Linux基本コマンドTips(53) - @IT
私がsedを覚えるために読み始めたのは@ITの記事なんですが、何も知らずに読み始めると、よく分かりませんでした。
sedコマンドを学ぶには、正確でなくてもよいので、ごく簡単な動作モデルを動かすと便利だ、というのが今回の話です。

繰り返しになりますが、素人の勉強ノートなので当てにはしないでおいてください。ちゃんとするならsedの仕様やソースを読まなきゃなんない気がするが、私にそういうスキルはありませんので。

sedひとつは「while read」1ループ分と見なす

sedを完全に真似することを目指さないならば、「while read」で真似できます。

#!/bin/sh
# 超強引に模擬したsed
# 入力をそのまま出力する

echo 'abc
abc
abc' | {
while read patternSpace
do


# 入力を1行読み込むごとにコマンドを実行する
# yコマンドやsコマンドなどを置く


# 自動出力
echo "${patternSpace}"

done
}
abc
abc
abc

「while read」のループひとつが、sedひとつです。
readで入力を1行読み込み、シェル変数「パターンスペース」に格納。
ループ末尾のechoで自動出力します。
sedのコマンドは、readとechoの間に、順番に置かれます。

注目すべき点は以下の2点です。

  • sedの引数に与えた「y/文字/文字/」などのコマンドは、ループの中に置かれたsedのコマンドである。

標準入力からの入力テキストが3行ならば、yコマンドも3回、実行されます

  • sedコマンドひとつは、大きなループひとつである。このループは、入力1行につき1回まわる。

sed -e 's/文字/文字/' -e 's/もじ/もじ/'」のように、「-e 'スクリプト'」がふたつあっても、ループがふたつ直列に並ぶわけではありません。

「引数ふたつ」ではなく「コマンドふたつ」

#!/bin/sh

# コマンドは並べた順番に実行される

echo 'abc
abc
abc' |
sed -e 'y/b/B/' -e 'y/B/X/'

echo 'abc
abc
abc' |
sed -e 'y/B/X/' -e 'y/b/B/'
aXc
aXc
aXc
aBc
aBc
aBc

sedに「-e」を2回書くと、書いた順番で実行されます。
これも「while read」で書き換えます。実行結果は同じです。

#!/bin/sh

echo 'abc
abc
abc' | {
while read patternSpace
do

patternSpace=$(echo "${patternSpace}" | tr 'b' 'B')
patternSpace=$(echo "${patternSpace}" | tr 'B' 'X')

echo "${patternSpace}"

done
}

echo 'abc
abc
abc' | {
while read patternSpace
do

patternSpace=$(echo "${patternSpace}" | tr 'B' 'X')
patternSpace=$(echo "${patternSpace}" | tr 'b' 'B')

echo "${patternSpace}"

done
}


複雑なsedスクリプトでは、複数の「行数d」や「s/文字/文字/」を「-e」やセミコロン「;」で区切って書き込みますが、これは「sedコマンドの動作オプションをたくさん与えている」「sedを何度も実行している」のではなく、「sed内部のsed専用コマンドを1行にまとめて書いている」、と理解すべきです。
「-e」オプションがふたつあっても、sed自体を実行している回数は1回です。

dコマンドの例

【 sed 】コマンド(基礎編その2)――行番号/パターンを指定して削除する:Linux基本コマンドTips(54) - @IT
d 指定した行を削除する


たとえば2行目を削除するsedスクリプトは、

echo '111
222
333' | sed -e '2d'

echo '111
222
333' | sed -n -e '2d;p'

このふたつの書き方があります。
しかしこれ、変な書き方にも見えます。「dコマンドは指定した行を削除するコマンドだ」と理解するなら、dコマンドに引数の2を与える、つまり「d 2」と書くはずです。
dコマンドの説明を別の解説から引用してみます。

ストリームエディタ sed
(delete)パターンスペースの内容を全削除し、以降のコマンドの実行をスキップして直ちに次のサイクルを開始する。


while read ループで真似します。

#!/bin/sh

echo '111
222
333' | {
# sed -e '2d'

# 行番号
lineNo='0'

while read patternSpace
do
# 行番号を数える
lineNo=$(( ${lineNo} + 1 ))

# dコマンド 2d
if test ${lineNo} -eq 2 ; then
{
continue
}
fi

# 自動出力機能
echo "${patternSpace}"

done
}
#!/bin/sh

echo '111
222
333' | {
# sed -n -e '2d;p'

# 行番号
lineNo='0'

while read patternSpace
do
# 行番号を数える
lineNo=$(( ${lineNo} + 1 ))

# dコマンド 2d
if test ${lineNo} -eq 2 ; then
{
continue
}
fi

# pコマンドによる出力命令
echo "${patternSpace}"

# 自動出力機能
# 「-n」オプションでコメントアウトされる
#echo "${patternSpace}"

done
}

sedにはアドレス指定の機能があります。
sedには内部に行番号カウンターがあり、ユーザが指示しなくても常に動作している。
アドレスを行指定するというのは、「もし行番号カウンタの数字が〇〇ならば、以下のコマンドを実行せよ」というif文です。

さらに、dコマンドは「以降のコマンドの実行をスキップして直ちに次のサイクルを開始する」のだから、continueで真似できます。

dコマンド自体で行を選んでいるのではなく、行番号カウンタ機能で行を選んでからdコマンドを実行している。だから「2{ d }」、括弧を省略して「2d」と書くわけです。

スクリプトならスクリプトっぽく書けばいい

awkの場合はスクリプト言語っぽく書くのに、sedでは「-e」やセミコロンをたくさん並べて書くのを見かけます。
端末でsedを使うなら短く書く必要があります。しかし、シェルスクリプトと組み合わせてファイルに書くなら、改行やコメント、必要のない括弧を入れることもできます。
sed -n -e '2d;p'」を以下のように書き換えてもいい。

echo '111
222
333' | sed -n -e '

# もし行番号カウンタが2ならば   if [ 行番号 = 2 ] then 〜
2{
# delete 次の行の処理に進む    continueのようなもの
d
# ↓の処理は実行しない
}

# 条件判定なしで常に実行するが、
# 2行目のみここまで来られない
{
# print 
p
}
'

テキストファイルにワンライナーで書く必要は、ありません。

(雑感)sedは中身が見えなくてよくわからん

ストリームエディタ sed
(delete)パターンスペースの内容を全削除し、以降のコマンドの実行をスキップして直ちに次のサイクルを開始する。

sed (コンピュータ) - Wikipedia
そして次のスクリプト「d」を評価し、入力データの行番号や内容に関係なく無条件に d コマンドを適用し、パターンスペースのデータを削除する。sedスクリプトの終わりに到達すると、パターンスペースにデータが残っている場合、パターンスペースの内容を出力して、次の入力データの処理に移る。このスクリプトでは、スクリプトの2行目で、パターンスペースのデータが削除されているため、データの出力はせずに次の入力データの処理に移る。


説明が違いますよねこれ。どっちが正しいのか、私には確かめるスキルが無い。

echo '1行目
2行目
3行目' | sed -e '

{ # すべての行で実行する

# ホールドスペースにコピー
# 入力文字のオリジナルをホールドスペースに保存
  h

# 現在のパターンスペースに目印の「_」を付加する
  s/^/_/

}


3{ # 3行目のみ実行する

# ホールドスペースの中身を消す
# 以降の処理を実行する? しない?

#  d

# ホールドスペースの中身をパターンスペースにコピー
# 「3行目」のオリジナル文字列をパターンスペースに戻す
  g
}

# 末尾で自動出力
'
_1行目
_2行目
3行目

「#d」を「d」にしてdコマンドを実行すると、

_1行目
_2行目

もし「dコマンドでパターンスペースが削除されるから、自動出力しない」なら。
削除してからでも、改めて文字をパターンスペースに入れたら、出力されるはずです。でも出ない。

「以降のコマンドの実行をスキップ」のほうが、実際の動作に近い気はするんですけどね。