初歩のシェルスクリプトで遊ぶ[sedのモヤモヤを整理したい(2)]
今回はパターンスペースとホールドスペースを、シェルスクリプト製の簡易sedに実装します。
内容は「整理したい(1)」で書いた内容の再説明になってますが、繰り返して噛み砕きます。
「2p」は「2行目を出力」ではなく「行番号カウンターが2のとき、変数『パターンスペース』内の文字を全て出力」
# 2行目にマッチして出力? $ seq 1 3 | sed -e '2p' 1 2 2 3
# 2行目にマッチして削除? $ seq 1 3 | sed -e '2d' 1 3
前回「整理したい(1)」で例にした、2行目を重複出力する例と、定番の2行目を削除するスクリプト。
特に「指定した行を削除するには」という目的で使われたり紹介されたりする例ですが、「2」は「2行目を指定して」と説明されることがあるようです。これが誤解の元になる。
「2行目を」というと、「入力されたテキストデータの2行目を」という意味にも聞こえます。場所なんでしょうか内容なんでしょうか。
もし、2行目と1行目の両方が、パターンスペースに入っていたら、どうなるんだろうか。sedがパターンスペースの内容を調べて、2行目の部分だけを相手にするんだろうか。
「2行目のテキストを」と「行番号カウンタが2ならば」は違う
https://linuxjm.osdn.jp/html/GNU_sed/man1/sed.1.html
説明書によると、sedのpコマンドは「パターンスペースの内容を出力する、プリントする」です。「アドレスで指定した行を出力する」のとは、どこかが違う。
$ seq 1 2 | sed -n -e '2p' 2 narr@UV:~$ seq 1 2 | sed -n -e '1h;2H;2x;2p' 1 2 narr@UV:~$ seq 1 2 | sed -n -e '1h;2H;2x;2p;2s/\n/_/g;2p' 1 2 1_2
自動出力を抑制する「-n」で静かにさせておいて、「2p」のみであれば、2行目のみ出力をする。
1行目をホールドスペースにコピーしておき、2行目をホールドスペースに追記して、ホールドスペースとパターンスペースを入れ替え、そして「2p」すると、1行目も出力されます。「1(改行)2」の状態で。「2p」が「2行目をプリントせよ」なら、1行目まで出てくるのはおかしい。
「1(改行)2」を、sコマンドで「改行をアンダースコアに入れ替えよ」と命令すると、「1_2」になる。最後の「2p」は、「2行目をプリント」では全くなくなっている。
たまたま「アドレス2を指定したときに、パターンスペースの中に2行目の内容のみが入っていた」ならば、2行目のみが出力される。もし「アドレス2」を指定したときに、1行目の内容も一緒にまとめてパターンスペースに入っていれば、1行目と2行目の内容が一緒に出力されてしまう。
sedの「行番号でのアドレス指定」を、「入力されたテキストデータの行を指定する」と理解すべきではありません。
「sed内部の行番号カウンタの数値を見て、『もし行番号カウンタが2ならば』という条件分岐をしている」と解釈したほうが、理解しやすくなります。
#!/bin/sh # ============================================================================= # ■ 簡易sed パターンスペースとホールドスペース # ============================================================================= # s s_cmd(){ # sedをそのまま埋め込んである。 # 改行は「\n」の代わりに「\o000」を使うこと XX=`printf "%s" "${PATTERN_SPACE}" | od -tx1 -An -v | sed -e "s/0a/00/g"` eval 'PATTERN_SPACE=`printf "%s\n" "${XX}" | xxd -r -p | sed -e "'"${1}"'" | sed -e "s/\o000/\n/g"`_' PATTERN_SPACE="${PATTERN_SPACE%_}" } # p:PSの内容を出力する p_cmd(){ printf '%s\n' "${PATTERN_SPACE}" } # パターンスペース(PS)とホールドスペース(HS)操作 --------------------------------- # h h_cmd(){ HOLD_SPACE="${PATTERN_SPACE}" } # H H_cmd(){ HOLD_SPACE="${HOLD_SPACE} ${PATTERN_SPACE}" } # g g_cmd(){ PATTERN_SPACE="${HOLD_SPACE}" } # G G_cmd(){ PATTERN_SPACE="${PATTERN_SPACE} ${HOLD_SPACE}" } # x x_cmd(){ buffer="${PATTERN_SPACE}" PATTERN_SPACE="${HOLD_SPACE}" HOLD_SPACE="${buffer}" } # ============================================================================= # 処理開始 # ============================================================================= # sedスクリプト内で直接に使うシェル変数_アドレス指定に使う NR='0' # 行番号 # 直接は使わないが操作するシェル変数 PATTERN_SPACE='' # パターンスペース HOLD_SPACE='' # ホールドスペース # 自動出力の抑制オプション Parm_n='-n' # メインループ開始 while IFS= read -r PATTERN_SPACE ; do # ==== 最初の1行読み込み ========= # 行番号を+1 NR=$(( ${NR} + 1 )) # ---- sedスクリプト記述領域/ ------------------------------------------ if test ${NR} -eq 1 ; then h_cmd fi if test ${NR} -eq 2 ; then H_cmd fi if test ${NR} -eq 2 ; then x_cmd fi if test ${NR} -eq 2 ; then s_cmd 's/\o000/_/g' fi if test ${NR} -eq 2 ; then p_cmd fi # ------------------------------------------ /sedスクリプト記述領域 ---- # ==== 自動出力 「-n」で抑制する ======================================= if test "${Parm_n}" != '-n' ; then printf '%s\n' "${PATTERN_SPACE}" fi done
$ seq 1 2 | ./sedSimple_PS_HS.sh 1_2
ホールドスペースの変化を見る
パターンスペースとホールドスペースで処理するsedスクリプトは、ややこしいといえばややこしいです。でも「変数の定形操作」だと思えば、そんなに難解な処理でもないように思えます。
新規に変数を作って追加、といったことはできないが、自由に使える変数が一つだけ用意されている。それがホールドスペースだ、くらいに理解するのが、手っ取り早い。
【 sed 】コマンド(応用編その3)――ホールドスペースの活用:Linux基本コマンドTips(214) - @IT
@ITの例を、シェルスクリプトで再現しつつ、さらにホールドスペースの内容の変化を見ます。
シェルスクリプトには「最後の行」の指定を実装してないので、行数を固定して数字で指定することにします。@ITの例だと、こう。
$ echo '1行目 2行目 3行目' | sed -n -e '1!G;h;$p' 3行目 2行目 1行目 $ echo '1行目 2行目 3行目' | sed -n -e '1!G;h;3p' 3行目 2行目 1行目
#!/bin/sh #set -x # ============================================================================= # ■ 簡易sed パターンスペースとホールドスペース # ============================================================================= # p:PSの内容を出力する p_cmd(){ printf '%s\n' "${PATTERN_SPACE}" } # パターンスペース(PS)とホールドスペース(HS)操作 --------------------------------- # h h_cmd(){ HOLD_SPACE="${PATTERN_SPACE}" } # H H_cmd(){ HOLD_SPACE="${HOLD_SPACE} ${PATTERN_SPACE}" } # g g_cmd(){ PATTERN_SPACE="${HOLD_SPACE}" } # G G_cmd(){ PATTERN_SPACE="${PATTERN_SPACE} ${HOLD_SPACE}" } # x x_cmd(){ buffer="${PATTERN_SPACE}" PATTERN_SPACE="${HOLD_SPACE}" HOLD_SPACE="${buffer}" } # ----【debug】--------------------------------------------------------- HS_PS_viewSub(){ ( printf '%s\n' "${HOLD_SPACE}" 1> "${holdSpaceTemp}" echo "${PATTERN_SPACE}" | paste -d "`printf '\034'`" - "${holdSpaceTemp}" | xargs -I '{}' echo "${NR}:"'{}' | column -t -s "`printf '\034'`" | sed -e 's/.*/\o033[30;102m&\o033[0m/' ) } # ============================================================================= # 処理開始 # ============================================================================= # ホールドスペース表示用 holdSpaceTemp=`mktemp` trap 'rm "${holdSpaceTemp}"' 0 1 3 15 # sedスクリプト内で直接に使うシェル変数_アドレス指定に使う NR='0' # 行番号 # 直接は使わないが操作するシェル変数 PATTERN_SPACE='' # パターンスペース HOLD_SPACE='' # ホールドスペース Parm_n='-n' # メインループ開始 while IFS= read -r PATTERN_SPACE ; do # ==== 最初の1行読み込み ========= # 行番号を+1 NR=$(( ${NR} + 1 )) # ---- sedスクリプト記述領域/ ------------------------------------------ # sed -ne '1!G;h;3p' if test ${NR} -ne 1 ; then G_cmd fi h_cmd if test ${NR} -eq 3 ; then p_cmd fi # ------------------------------------------ /sedスクリプト記述領域 ---- # ==== 自動出力 「-n」で抑制する ======================================= if test "${Parm_n}" != '-n' ; then printf '%s\n' "${PATTERN_SPACE}" fi HS_PS_viewSub done