ホールドスペースはパターンスペースとは別の保存領域であり、次の入力行サイクルになっても内容が残る。パターンスペースとの間でコピー・追記のやりとりができ、より複雑な処理に使う。
h
コマンド: パターンスペースからホールドスペースへコピー。H
コマンド: ホールドスペースの末尾を改行した上で、パターンスペースからホールドスペースへ追記。g
コマンド: ホールドスペースからパターンスペースへコピー。G
コマンド: パターンスペースの末尾を改行した上で、ホールドスペースからパターンスペースへ追記。x
コマンド: パターンスペースとホールドスペースを交換。
ホールドスペースは次の入力行のサイクルになっても内容が維持される保存領域であり、アドレスや上記以外のコマンド(p
やs
コマンドなど)を直接適用することはできない。
なお、下記は大文字コマンドでは「改行した上で追記」されることを示す例。
echo 'A
B' | sed '/B/ { H;x; }'
A
B
小文字コマンドならこうなる。
echo 'A
B' | sed '/B/ { h;x; }'
A
B
下記は改行がメチャクチャな文の不要な改行を取り除く例。
input='改行がメチャクチャの1
行
目。
2行
目もめちゃ
く
ちゃ。
3行目も
め
ちゃくちゃでした。'
script='
s/^[[:blank:]]+// # 行頭スペース削除
H # 行末句点まではホールドスペースへ追記していく
/。$/ {
x # パターンスペースとホールドスペースの入れ替え
s/\n//g # 改行削除
p
s/.*// # パターンスペースを空にして、
h # 空をホールドスペースへコピー
}'
echo "$input" | sed --regexp-extended --quiet "$script"
出力はこうなる。
改行がメチャクチャの1行目。 2行目もめちゃくちゃ。 3行目もめちゃくちゃでした。
単純なブロック構造ならアドレス2個の正規表現で検出できるが、下記のように入れ子のあるブロックだと簡単にはいかない。正規表現アドレスだけでは入れ子ブロック終了なのか本体ブロック終了なのか区別が付けられない。
blockA {
blockB {
}
blockC {}
} # ここがblockAの終了
そこで、サイクルを通じて情報保存ができるホールドスペースに「今どれくらい入れ子なのか」を保持することで解決してみる。(裏技的だし、sedでこれを解決すべきかは..)
具体的には、ブロックに入るたびにホールドスペースに「子」という一文字を追記し、ブロックを出るたびに一文字消し、出るときに「子」が一つだけなら本体終了と判断する。下記は、ブロック終了行の行番号・内容を表示する例で、ラベルも利用している。
input='# ブロック終了を検出したい
blockX {
# 入れ子になっている
blockA {
blockB {
}
blockC {} # こんな1行完結ブロックも
}
} # blockXの終了
block {
}'
# アドレスに使う正規表現
target='^[[:blank:]]*blockX[[:blank:]]*\{'
# 1行完結ブロックはマッチしないようにする
block_start='^[[:blank:]]*block.[[:blank:]]*\{[^}]*$'
block_end='^[[:blank:]]*\}'
# sedスクリプト
script="
/$target/ {
b ブロックプラス
}
# 以下の!{}内はラベルでしか到達できない
! {
: ループ
/$block_end/ {
# 終了判定
x
/^子$/ {
x
= # 行番号表示
p
q
}
x
b ブロックマイナス
}
/$block_start/ b ブロックプラス
n
b ループ
}
# ブロックに入るたびにホールドスペースに「子」を一つ追加
! {
: ブロックプラス
x
s/^/子/
x
n
b ループ
}
# ブロックから出るたびにホールドスペースから「子」を一つ削除
! {
: ブロックマイナス
x
s/^子//
x
n
b ループ
}
"
echo "$input" | sed --regexp-extended --quiet "$script"
上記の出力はこうなる。
9 } # blockXの終了