【Sed】一行野郎と複数行野郎

・シェルスクリプトと組み合わせて sed を使用した場合のプログラム。
・Linuxコマンドやシェルスクリプトで簡単に実現できることは、sed で苦労して実現させるようなことはしない
・作成時のシェル環境が fish だったため、「一行野郎」が他のシェルでは動かないかも知れない

  • 具体的には、コマンドではエスケープシーケンス「\」の数が bash よりも多いかも知れない
  • 従って、うまく動かない場合は、「\」の個数を半分に減らしてみること

一行野郎

文字列置換系
  行末の余計な空白(スペースやタブ)を削除する
  HTML や XML タグを取り除く
  ファイル中の単語「BEGIN」を「HOGE」に変える
  行全体をカッコ() で囲む
  特定の文字列を含む行を削除する
  最終行のみに対してパターンマッチを行う
  連続するタブスペースもしくは半角スペースを半角スペース1個にする
    「{n,}」使用例
    「{n,}」使用例
    「+」使用例

行連結系
  行末が「\」の場合のみ、次行と連結させる
  2行(n行とn+1行)を連結する
  3行を1行にまとめる場合
  全ての行を連結する
  空白行が複数個連続で続いた場合は空白行を1行にまとめる
  行番号を付けて表示する (cat -n 相当)
範囲選択
  BEGIN から END の範囲を表示する。(BEGIN と END の行も含む)
大文字・小文字変換
  ファイル内の全ての文字を大文字にする
  ファイル内の全ての文字を小文字にする
  ファイル内の単語の先頭1文字を大文字にする
  ファイル内の単語の先頭1文字を小文字にする


 
複数行野郎

SSL サーバ証明書の Validity の区間のみを抽出する
  get_lines_from_cert.sh
空行で無ければ行全体を < > で囲う
  surround_all_lines_skip_emptyline.sh
ファイルの中身をすべて大文字にする
  to_UpperG.sh
ファイルの中身をすべて小文字にする
  to_LowerG.sh
ファイルの中身の単語の先頭1文字を大文字にする
  to_Upper.sh
ファイルの中身の単語の先頭1文字を小文字にする
  to_Lower.sh
awk の BEGIN 〜 END を表示する
  cat_awk.sh
awk の BEGIN 〜 END 以外を表示する
  skip_awk.sh

 

一行野郎

最終行のみに対してパターンマッチを行う

  • Linux 64ビット向け Google Chrome Headless の最新バージョンを表示する
% wget https://chromedriver.storage.googleapis.com/ -O - |
    xmllint --format - --xpath="/ETag/Key/" | 
    grep -i linux64 | 
    sort -t'.' -k 1,1n -k 2,2n |
    sed -n '$s%<Key>\(.*\)</Key>%\1%p' 

 

空白行が複数個連続で続いた場合は、空白行を1行に束ねる

% sed -e '/^$/ { N; /^\n$/D }'

 

行末の余計な空白(スペースやタブ)を削除する

  • 行の末尾に何か挿入する場合などの事前準備として本処理をすることが多い気がする。
% sed  's/[[:space:]]+*$//g'

 

HTML や XML タグを取り除く

  • 単に取り除くと見づらくなるので、半角スペースに置き換えるサンプルにする
  • なお、sed でうまくタグが取り除けない場合は、sed で四苦八苦せずに専門のコマンドに任せた方が良いと思う
% sed -e 's/<[^>]*>/ /g' $1

 

行末が「\」の場合のみ、次行と連結させる

  • fish の場合
% sed -e ' :loop /\\\\$/ { N; s%\(.*\)\n\(.*\)%\1\2%g; b loop }' $1
  • bash の場合
% sed -e ' :loop /\\$/ { N; s%\(.*\)\n\(.*\)%\1\2%g; b loop }' $1
  • 複数行に分けて書くと次のようになる
#!/bin/sh
sed -e '
:loop
/\\$/ {
    N
    s%\(.*\)\n\(.*\)%\1\2%g
    b loop
}' $1

 

ファイル中の単語「BEGIN」を「HOGE」に変える

sed -i 's/\<BEGIN\>/HOGE/g' $1

 

n行と n+1行を連結する

  • 2行を連結する処理
  • N コマンド (=ホールドパターンに次の行を読み込む) を使う
    • バッファ=作業領域=ホールドパターンは "a\nb" という状態
    • "a\nb" に対して s コマンドで "\n" を取り除いている
      • 何度も繰り返せば、最終的に1行にできるということ (実際の方法はこちら)
% echo -e "a\nb\nc\nd\ne"
a
b
c
d
e

N コマンドと s コマンドを使う

% echo -e "a\nb\nc\nd\ne" | sed 'N;s/\n//'
ab
cd
e

 

3行を1行にまとめる場合

  • N を2個並べる
  • s コマンドに g を付与する → 's/\n//g'
% echo -e "a\nb\nc\nd" |  sed 'N;N;s/\n//g'

 

全ての行を連結する

  • n行とn+1行の連結処理に次の処理を加えると実現可能になる
    • 「:ラベル」と「b ラベル」で入力が終わるまでループさせる
% echo -e "a\nb\nc\nd\ne" | sed '{ :loop ; N; s/\n//; b loop }'

 

行番号を付けて表示する (cat -n 相当)

sed の = を使うことで行番号が表示できるが、次のように改行が入ってしまう

% echo -e "a\nb" | sed '='
1
a
2
b

Nコマンド と sコマンドを使って、改行を取り除く

% echo -e "a\nb" | sed '=' | sed 'N;s/\n//'
1a
2b

Nコマンド実行後は「 \(.*\)\n\(.*\) 」というパターンになっているので、「\n」を適当な空白に変えれば見易くなる

% echo -e "a\nb" | sed '=' | sed 'N;s/\n/    /'
1    a
2    b

 

BEGIN から END の範囲を表示する。BEGIN と END の行も含む。

awk の BEGIN 〜 END を表示する処理。

% sed -n -e '/\<BEGIN\>/,/\<END\>/p'

 

BEGIN 〜 END 以外の範囲を表示する。

sed -n -e '/\<BEGIN\>/,/\<END\>/!p'

 

ファイル内の全ての文字を大文字にする

% sed -n -e 's/\<./\U&/gp' $1
% sed     -e 's/\<./\U&/g'   $1

 

ファイル内の全ての文字を小文字にする

% sed -n -e 's/\<./\L&/gp' $1
% sed     -e 's/\<./\L&/g'   $1

 

ファイル内の単語の先頭1文字を大文字にする

% sed -n -e 's/\<./\u&/gp' $1
% sed     -e 's/\<./\u&/g'   $1

 

ファイル内の単語の先頭1文字を小文字にする

% sed -n -e 's/\<./\l&/gp' $1
% sed     -e 's/\<./\l&/g'   $1

 

特定の文字列を含む行を削除する

特定の文字列を含む行を削除する

  • find の結果から「/var/log/samba」を含む行を削除している
  • 「検索パターン」の「\」を使うことで区切り文字を「/」から「@」に変える
    • これにより、余計なエスケープシーケンスの付与を回避できる
% find /var/log/ -maxdepth 2 | sed '\@^/var/log/samba@d'

 

行全体をカッコ() で囲む

  • 行全体をカッコ() で囲む。(空白行のみもカッコでく括ってしま)
#!/bin/sh
sed -n 's/.*/(&)/p' $1

 

連続するタブスペースもしくは半角スペースを半角スペース1個にする

「{n,}」使用例: 直前に指定した文字([:space:])が 1個以上あれば
echo " 		 	 " | sed 's/[[:space:]]\{1,\}/ /g'
「{n,}」使用例: 直前に指定した文字([:space:])が 2個以上あれば..
echo " 		 	 " | sed 's/[[:space:]]\{2,\}/ /g'
「+」使用例: 直前に指定された正規表現が1個以上並んでいれば..
echo " 		 	 " | sed 's/[[:space:]]\+/ /g'


 

複数行野郎

get_lines_from_cert.sh

  • 「行頭が空白で "Validity" を含む行」から「行頭が空白で "Subject:" を含む」行までを一旦抽出するプログラム
  • sed -n '2,$p' で 1行目("Validity"を含む)は捨てて、sed '$d' で最終行("Subject:"を含む)を捨てている
  • 入力データである openssl x509 -text -in google.com.crt の中身も記載しておく
#!/bin/sh
openssl x509 -text -in google.com.crt |
sed -n '{ /^[[:space:]]*Validity/,/^[[:space:]]*Subject:/p } '  |
sed -n '2,$p' | # 先の結果の2行目から最終行までを表示する
sed '$d'        # 最終行は削除する
#=> Not Before: Dec  1 14:22:07 2016 GMT
#   Not After : Feb 23 14:17:00 2017 GMT</span>

 

surround_all_lines_skip_emptyline.sh

  • 空行以外の場合
    • 行全体を < > で囲う
  • 空行の場合
    • 何も処理をせずに表示するのみ
#!/bin/sh
sed -n -e '
/^[ \t]*$/! {   # 空白行では無い場合
    s/.*/<&>/p  # 行全体を < > で囲う
}
/^[ \t]*$/ {    # 空行もしくは空白行の場合
    p           # 単に表示するのみ
}
' $1

 

to_UpperG.sh

ファイルの中身すべてを大文字にする。

  • マルチバイト文字が崩れることは早々無いはず..
#!/bin/sh
sed -n -e '
/^[ \t]*$/! {   # 空白行では無い場合
    s/.*/\U&/p  # 行全体を大文字にする
}
/^[ \t]*$/ {    # 空行もしくは空白行の場合
    p           # 単に表示するのみ
}
' $1

 

to_LowerG.sh

ファイルの中身すべてを文字にする。

#!/bin/sh
sed -n -e '
/^[ \t]*$/! {   # 空白行では無い場合
    s/.*/\L&/p  # 行全体を小文字にする
}
/^[ \t]*$/ {    # 空行もしくは空白行の場合
    p           # 単に表示するのみ
}
' $1

 

to_Upper.sh

ファイルの中身のすべての単語の先頭1文字を大文字にする。

  • なお、2行目の -n オプションを使用しない場合は以下の対応をすること。
    • 対応しないと2行分表示されてしまう
      • 4行目の「 s/\<./\u&/gp 」を「 s/\<./\u&/g 」にすること
      • 7行目の「 p 」を削除すること
#!/bin/sh
sed -n -e '
/^[ \t]*$/! {       # 空白行では無い場合
    s/\<./\u&/gp    # 単語の先頭1文字を大文字にする
}
/^[ \t]*$/ {        # 空行もしくは空白行の場合
    p               # 単に表示するのみ
}
' $1

 

to_Lower.sh

ファイルの中身のすべての単語の先頭1文字文字にする。

  • なお、2行目の -n オプションを使用しない場合は以下の対応をすること。
    • 対応しないと2行分表示されてしまう
      • 4行目の「 s/\<./\l&/gp 」を「 s/\<./\l&/g 」にすること
      • 7行目の「 p 」を削除すること
#!/bin/sh
sed -n -e '
/^[ \t]*$/! {       # 空白行では無い場合
    s/\<./\l&/gp    # 単語の先頭1文字を小文字にする
}
/^[ \t]*$/ {        # 空行もしくは空白行の場合
    p               # 単に表示するのみ
}
' $1

 

cat_awk.sh

BEGIN 〜 END の範囲を表示するプログラム

  • コメント部に「BEGIN」や「END」があれば次の行に進む(=スキップ=nコマンド)をしている
  • 本番環境では 10行目付近の「/^#/{ n } 」を有効にすれば、コメントの大半は消せる
#!/bin/sh

#「#」が 1個と、それに続く文字が 1個以上あり、「BEGIN」が出現する行は削除する
# sed -n -e ' /[#]\+.*\<BEGIN\>/! { /\<BEGIN\>/,/\<END\>/p }' $1
#
#「#」が 1個と、それに続く文字が 0個以上あり、「BEGIN」が出現する行は削除する
# sed -n -e ' /[#].*\<BEGIN\>/! { /\<BEGIN\>/,/\<END\>/p }' $1

sed -n -e '
# /^#/ { n }         # 本番では、保護処理として行頭「#」はコメントとして読み飛ばす
/[#]\+.*\<BEGIN\>/ { #「^# BEGIN」を含めて、コメントの BEGIN は無視する
    n
}
/[#]\.*\<BEGIN\>/  { #「^#BEGIN」を含めて、コメントの BEGIN は無視する
    n
}
/[#]\+.*\<END\>/ {  #「^# END」を含めて、コメントの END は無視する
    n
}
/[#]\.*\<END\>/  { #「^#END」を含めて、コメントの END は無視する
    n
}
{
    /\<BEGIN\>/,/\<END\>/p
}' $1

 

skip_awk.sh

AWK の BEGIN 〜 END 以外を表示するプログラム

#!/bin/sh
sed -n -e '
/[#]\+.*\<BEGIN\>/ { #「^# BEGIN」を含めて、コメントの BEGIN は無視する
    p
}
/[#]\.*\<BEGIN\>/  { #「^#BEGIN」を含めて、コメントの BEGIN は無視する
    p
}
/[#]\+.*\<END\>/ {  #「^# END」を含めて、コメントの END は無視する
    p
}
/[#]\.*\<END\>/  { #「^#END」を含めて、コメントの END は無視する
    p
}
{
    /\<BEGIN\>/,/\<END\>/!p
}' $1