[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

4. いくつかの見本スクリプト

以下は,sedをマスターするためのガイドとなるいくつかのsedス クリプトの....

Some exotic examples:
4.1 行の中央揃え  
4.2 数字を増加させる  
4.3 ファイル名を小文字に変更する  
4.4 bashの環境変数の出力  
4.5 行の文字を反転する  
Emulating standard utilities:
4.6 ファイルの行を反転する  Reverse lines of files
4.7 行の番号付け  Numbering lines
4.8 空白行以外に番号を付ける  Numbering non-blank lines
4.9 文字を数える  Counting chars
4.10 単語を数える  Counting words
4.11 行を数える  Counting lines
4.12 最初の行を出力する  Printing the first lines
4.13 最後の行を出力する  Printing the last lines
4.14 重複した行を一行にする  Make duplicate lines unique
4.15 入力の重複している行を出力する.  Print duplicated lines of input
4.16 すべての重複行を削除する  Remove all duplicated lines
4.17 空白行をまとめる  Squeezing blank lines


4.1 行の中央揃え

以下のスクリプトは,ファイルのすべての行を80桁の幅でセンタリングします. 幅を変更するため,\{\}の数値を変更する必要があり,追加されるス ペースも変更する必要があります.

マッチさせる正規表現の部分を分離するため,バッファコマンドが使用されてい る方法に注意してください -- これは一般的なテクニックです.

 
#!/usr/bin/sed -f

# Put 80 spaces in the buffer
1 {
  x
  s/^$/          /
  s/^.*$/&&&&&&&&/
  x
}

# del leading and trailing spaces
y/tab/ /
s/^ *//
s/ *$//

# add a new-line and 80 spaces to end of line
G

# keep first 81 chars (80 + a new-line)
s/^\(.\{81\}\).*$/\1/

# \2 matches half of the spaces, which are moved to the beginning
s/^\(.*\)\n\(.*\)\2/\2\1/


4.2 数字を増加させる

以下のスクリプトは,sedで算数を行なう方法を説明するものの一つです. これは実際には可能ですが(6),手動で行なうべきでしょう.

数値を一つ増加させるには,最後の桁に1を追加し,それに続く桁を置換するだ けです.例外が一つあります.その桁の前の数値が9のとき,9が無くなるまで増 加させる必要もあります.

このBruno Haibleによる解決方法は,単一のバッファを使用しているので非常に 賢く知的です.この制限がない場合,Numbering linesで使用さ れているアルゴリズムの方がより速いでしょう.それは後置される9をアンダー スコアで置換し,複数のsコマンドを最後の桁を増加させるために使用し, そして,再びアンダースコアをゼロで置換することで動作します.

 
#!/usr/bin/sed -f

/[^0-9]/ d

# replace all leading 9s by _ (any other char except digits, could
# be used)
:d
s/9\(_*\)$/_\1/
td

# incr last digit only.  The first line adds a most-significant
# digit of 1 if we have to add a digit.
#
# The tn commands are not necessary, but make the thing
# faster

s/^\(_*\)$/1\1/; tn
s/8\(_*\)$/9\1/; tn
s/7\(_*\)$/8\1/; tn
s/6\(_*\)$/7\1/; tn
s/5\(_*\)$/6\1/; tn
s/4\(_*\)$/5\1/; tn
s/3\(_*\)$/4\1/; tn
s/2\(_*\)$/3\1/; tn
s/1\(_*\)$/2\1/; tn
s/0\(_*\)$/1\1/; tn

:n
y/_/0/


4.3 ファイル名を小文字に変更する

以下はちょっと変わったsedの使用方法です.我々はテキストを変換し, それをシェルコマンドに変換し,そしてそれらをそのままシェルに与えます. sedを使用するとき,更に悪いことになっても気にしないでください. dateの出力をbcプログラムに変換するスクリプトを見たことだっ てあります!

これのメインの本体はsedスクリプトで,名前を小文字から大文字(また はその逆に)に置き換え,置き換えられた名前がオリジナルの名前と同じ場合で も適用します.スクリプトがシェル変数を使用して媒介している方法と,適切に 引用符で囲んでいる方法に注意してください.

 
#! /bin/sh
# rename files to lower/upper case... 
#
# usage: 
#    move-to-lower * 
#    move-to-upper * 
# or
#    move-to-lower -R .
#    move-to-upper -R .
#	

help()
{
	cat << eof
Usage: $0 [-n] [-r] [-h] files...

-n      do nothing, only see what would be done
-R      recursive (use find)
-h      this message
files   files to remap to lower case

Examples:
       $0 -n *        (see if everything is ok, then...)
       $0 *

       $0 -R .

eof
}

apply_cmd='sh'
finder='echo $* | tr " " "\n"'
files_only=

while :
do
    case "$1" in 
        -n) apply_cmd='cat' ;;
        -R) finder='find $* -type f';;
        -h) help ; exit 1 ;;
        *) break ;;
    esac
    shift
done

if [ -z "$1" ]; then
        echo Usage: $0 [-n] [-r] files...
        exit 1
fi

LOWER='abcdefghijklmnopqrstuvwxyz'
UPPER='ABCDEFGHIJKLMNOPQRSTUVWXYZ'

case `basename $0` in
        *upper*) TO=$UPPER; FROM=$LOWER ;;
        *)       FROM=$UPPER; TO=$LOWER ;;
esac
	
eval $finder | sed -n '

# remove all trailing slashes
s/\/*$//

# add ./ if there are no path, only filename
/\//! s/^/.\//

# save path+filename
h

# remove path
s/.*\///

# do conversion only on filename
y/'$FROM'/'$TO'/

# now line contains original path+file, while
# hold space contains the new filename
x

# add converted file name to line, which now contains
# path/file-name\nconverted-file-name
G

# check if converted file name is equal to original file name,
# if it is, do not print nothing
/^.*\/\(.*\)\n\1/b

# now, transform path/fromfile\ntofile, into
# mv path/fromfile path/tofile and print it
s/^\(.*\/\)\(.*\)\n\(.*\)$/mv \1\2 \1\3/p

' | $apply_cmd


4.4 bashの環境変数の出力

以下のスクリプトは,set Bourneシェルコマンドの出力から,シェル関 数の定義を取り除きます.

 
#!/bin/sh

set | sed -n '
:x

# if no occurrence of `=()' print and load next line
/=() /! { p; b; }

# possible start of functions section
# save the line in case this is a var like FOO="() "
h

# if the next line has a brace, we quit because
# nothing comes after functions
n
/^{/ q

# print the old line
x; p

# work on the new line now
x; bx
'


4.5 行の文字を反転する

以下のスクリプトは,行の文字の位置を反転するために使用することが可能です. 二つの文字を同時に移動するテクニックで,直観的な実装より高速になります.

ラベル定義の前のtxコマンドに注意してください.これはtコマ ンドでテストされるフラグをリセットするために必要になることがよくあります.

想像力豊かな読者は,このスクリプトの使い方が分かるでしょう.例えば, bannerの出力を反転させることです(7)

 
#!/usr/bin/sed -f

/../! b

# Reverse a line.  Begin embedding the line between two new-lines
s/^.*$/\
&\
/

# Move first character at the end.  The regexp matches until
# there are zero or one characters between the markers
tx
:x
s/\(\n.\)\(.*\)\(.\n\)/\3\2\1/
tx

# Remove the new-line markers
s/\n//g


4.6 ファイルの行を反転する

以下のものは,様々なUnixコマンドをエミュレートする全く意味がない(面白い けどね)スクリプトです.これは特にtacと同等の動作をします.

GNU sedGNU sed以外の実装では,このスクリプトは簡単に 内部バッファでオーバーフローする可能性があることに注意してください.

 
#!/usr/bin/sed -nf

# reverse all lines of input, i.e. first line became last, ...

# from the second line, the buffer (which contains all previous lines)
# is *appended* to current line, so, the order will be reversed
1! G

# on the last line we're done -- print everything
$ p

# store everything on the buffer again
h


4.7 行の番号付け

以下のスクリプトはcat -nの置換えです.実際それは,出力をGNU catのように正確に書式化します.

もちろん,これは二つの理由から全く意味がありません.まず始めに他のものは Cで行ないます.二番目に以下のBourneシェルスクリプトは同じ目的で使用され, はるかに速くなります.

 
#! /bin/sh
sed -e "=" $@ | sed -e '
  s/^/      /
  N
  s/^ *\(......\)\n/\1  /
'

それは行番号を出力するためにsedを使用し,二つのNで行を二つにまと めます.もちろん,このスクリプトは以下で提示するものほど教わるものはあり ません.

増加で使用しているアルゴリズムを両方のバッファで使用しているので,行は可 能な限り速く出力され,そして破棄されます.数値は変更した桁がバッファに入 り,変更されないものがもう一方に行くように分離されています.変更される桁 は単一のステップ(yコマンドを使用して)修正されます.次の行の行番号 は,次の繰り返しで使用されるように,作成されホールドスペースに保存されま す.

 
#!/usr/bin/sed -nf

# Prime the pump on the first line
x
/^$/ s/^.*$/1/

# Add the correct line number before the pattern
G
h

# Format it and print it
s/^/      /
s/^ *\(......\)\n/\1  /p

# Get the line number from hold space; add a zero
# if we're going to add a digit on the next line
g
s/\n.*$//
/^9*$/ s/^/0/

# separate changing/unchanged digits with an x
s/.9*$/x&/

# keep changing digits in hold space
h
s/^.*x//
y/0123456789/1234567890/
x

# keep unchanged digits in pattern space
s/x.*$//

# compose the new number, remove the new-line implicitly added by G
G
s/\n//
h


4.8 空白行以外に番号を付ける

cat -bのエミュレートは,ほとんどcat -nと同じです -- 我々 は,番号を付ける行と付けない行を選択する必要があっただけです.

このスクリプトの前回ものとの共通部分には,適切なsedスクリプトへの コメントがいかに重要かを表示するコメントを付けていません....

 
#!/usr/bin/sed -nf

/^$/ {
  p
  b
}

# Same as cat -n from now
x
/^$/ s/^.*$/1/
G
h
s/^/      /
s/^ *\(......\)\n/\1  /p
x
s/\n.*$//
/^9*$/ s/^/0/
s/.9*$/x&/
h
s/^.*x//
y/0123456789/1234567890/
x
s/x.*$//
G
s/\n//
h


4.9 文字を数える

以下のスクリプトは,sedで算数を行なうもう一つの方法を提示していま す.この状況では,我々は可能な限り大きな数を追加する必要があるので,これ を正しく増加するように実装することは不可能でしょう(そして,おそらくこの スクリプトより計算がより複雑になるでしょう...).

数字を文字に割り当てるアプローチは,sedを用いたソロバンの実装の一 種です.`a'は一の位,`b'は十の位などになっています.我々は,一 の位として現在の行に文字の数を単純に追加し,十の位,百の位などに繰り上げ 伝搬させています.

通常通り,実行時の総数はホールドスペースに保持されます.

最後の行で,ソロバンを十進数の形式に戻しています.多様性のため,これは80 のsコマンドではなく,ループを用いて行なっています(8).最初に一の 位を変換し,`a'を数値から削除します.そして十の位が`a'になるよ うに文字を回転させ,残っている文字が無くなるまでそれを続けます.

 
#!/usr/bin/sed -nf

# Add n+1 a's to hold space (+1 is for the new-line)
s/./a/g
H
x
s/\n/a/

# Do the carry.  The t's and b's are not necessary,
# but they do speed up the thing
t a
: a;  s/aaaaaaaaaa/b/g; t b; b done
: b;  s/bbbbbbbbbb/c/g; t c; b done
: c;  s/cccccccccc/d/g; t d; b done
: d;  s/dddddddddd/e/g; t e; b done
: e;  s/eeeeeeeeee/f/g; t f; b done
: f;  s/ffffffffff/g/g; t g; b done
: g;  s/gggggggggg/h/g; t h; b done
: h;  s/hhhhhhhhhh//g

: done
$! {
  h
  b
}

# On the last line, convert back to decimal

: loop
/a/! s/[b-h]*/&0/
s/aaaaaaaaa/9/
s/aaaaaaaa/8/
s/aaaaaaa/7/
s/aaaaaa/6/
s/aaaaa/5/
s/aaaa/4/
s/aaa/3/
s/aa/2/
s/a/1/

: next
y/bcdefgh/abcdefg/
/[a-h]/ b loop
p


4.10 単語を数える

このスクリプトは,前回のものとほとんど同じで,行にあるそれぞれの単語を単 一の`a'に一度変換します(前回のスクリプトでは,それぞれの文字を `a'に変更しています).

本物のwcプログラムはwc -cに対しループが最適化されているの で,文字を数えるより単語を数える方がはるかに遅くなります.これらのスクリ プトのボトルネックは,どちらかというと算数にあり,このため,単語を数える ものはより速くなります(より小さい数を管理する必要があります).

前回同様,共通部分にはsedスクリプトへのコメントの重要性を示すコメ ントがありません.

 
#!/usr/bin/sed -nf

# Convert words to a's
s/[ tab][ tab]*/ /g
s/^/ /
s/ [^ ][^ ]*/a /g
s/ //g

# Append them to hold space
H
x
s/\n//

# From here on it is the same as in wc -c.
/aaaaaaaaaa/! bx;   s/aaaaaaaaaa/b/g
/bbbbbbbbbb/! bx;   s/bbbbbbbbbb/c/g
/cccccccccc/! bx;   s/cccccccccc/d/g
/dddddddddd/! bx;   s/dddddddddd/e/g
/eeeeeeeeee/! bx;   s/eeeeeeeeee/f/g
/ffffffffff/! bx;   s/ffffffffff/g/g
/gggggggggg/! bx;   s/gggggggggg/h/g
s/hhhhhhhhhh//g
:x
$! { h; b; }
:y
/a/! s/[b-h]*/&0/
s/aaaaaaaaa/9/
s/aaaaaaaa/8/
s/aaaaaaa/7/
s/aaaaaa/6/
s/aaaaa/5/
s/aaaa/4/
s/aaa/3/
s/aa/2/
s/a/1/
y/bcdefgh/abcdefg/
/[a-h]/ by
p


4.11 行を数える

sedはにwc -lの機能があるので,今回はおかしなことを何もしま せん!!! まあ見てください.

 
#!/usr/bin/sed -nf
$=


4.12 最初の行を出力する

以下のスクリプトは,おそらく最も単純で役に立つsedスクリプトです. それは入力の最初の十行を表示します.表示される行の数は,qコマンド の前と同じです.

 
#!/usr/bin/sed -f
10q


4.13 最後の行を出力する

最初ではなく最後のn行出力することはより複雑ですが実現可能です. nは,文字を駄目にする前に二行目でエンコードされます.

このスクリプトは,最終的な出力をホールドスペースに保持し,最後に出力する tacスクリプトに似ています.

 
#!/usr/bin/sed -nf

1! {; H; g; }
1,10 !s/[^\n]*\n//
$p
h

そのスクリプトの中心では,10行のウィンドウを保持し,行を追加し最も古い行 を削除しながらスライドしていきます(二行目の置換コマンドはDコマン ドのように動作しますがループを再開しません).

"スライドウィンドウ"のテクニックは,効率的で複雑なsedを書く強力 な方法で,それは,Pのようなコマンドを手動で実装する場合は多くの作 業が必要になるためです.

この章の残りで十分に説明している,NP,そしてDコマ ンドを基本としているテクニックを導入するため,単純な`スライドウィンドウ' を使用しているtailの実装を以下に上げます.

これは複雑に見えますが,実際最後のスクリプトと同じように動作します.しか し,適切な行数を捨てた後で,内部の行の場所を保持するためのホールドスペー スを使用するために停止し,パターンスペースを一行スライドするために NDを代わりに使用しています.

 
#!/usr/bin/sed -f

1h
2,10 {; H; g; }
$q
1,9d
N
D


4.14 重複した行を一行にする

以下は,NP,そしてDコマンドを使用した,おそらくマ スターするのが最も難しい芸術的な例です.

 
#!/usr/bin/sed -f
h

:b
On the last line, print and exit
$b
N
/^\(.*\)\n\1$/ {
    # The two lines are identical.  Undo the effect of
    # the n command.
    g
    bb
}

# If the N command had added the last line, print and exit
$b

# The lines are different; print the first and go
# back working on the second.
P
D

御覧のように,PDを使用して二行のウィンドウを管理していま す.このテクニックは,高度なsedスクリプトでよく使用されます.


4.15 入力の重複している行を出力する.

以下のスクリプトは,uniq -dのように重複している行だけを出力します.

 
#!/usr/bin/sed -nf

$b
N
/^\(.*\)\n\1$/ {
    # Print the first of the duplicated lines
    s/.*\n//
    p

    # Loop until we get a different line
    :b
    $b
    N
    /^\(.*\)\n\1$/ {
        s/.*\n//
        bb
    }
}

# The last line cannot be followed by duplicates
$b

# Found a different one.  Leave it alone in the pattern space
# and go back to the top, hunting its duplicates
D


4.16 すべての重複行を削除する

以下のスクリプトは,uniq -uのようにユニークな行だけを出力します.

 
#!/usr/bin/sed -f

# Search for a duplicate line --- until that, print what you find.
$b
N
/^\(.*\)\n\1$/ ! {
    P
    D
}

:c
# Got two equal lines in pattern space.  At the
# end of the file we simply exit
$d

# Else, we keep reading lines with N until we
# find a different one
s/.*\n//
N
/^\(.*\)\n\1$/ {
    bc
}

# Remove the last instance of the duplicate line
# and go back to the top
D


4.17 空白行をまとめる

最後の例として,空白行をまとめるcat -sと同じ機能を実装している, 複雑さと速度を上げていく三つのスクリプトを以下に書きます.

最初のものは,最初に空白行を取り去り,まだ残っていれば最後に取り去ります.

 
#!/usr/bin/sed -f

# on empty lines, join with next
# Note there is a star in the regexp
:x
/^\n*$/ {
N
bx
}

# now, squeeze all '\n', this can be also done by:
# s/^\(\n\)*/\1/
s/\n*/\
/

以下のものはより複雑で,最初にすべての空の行を取り除きます.まだ残ってい る場合,最後に単一の空白行を取り去ります.

 
#!/usr/bin/sed -f

# delete all leading empty lines
1,/^./{
/./!d
}

# on an empty line we remove it and all the following
# empty lines, but one
:x
/./!{
N
s/^\n$//
tx
}

以下は,前置および後置されている空白行を取り除きます.それは最も速いもの です.sedが行の終りで自動的にスクリプトの最初のサイクルに戻るとい う事実を利用することなく,nbを用いて複雑なループを実行し ていることに注意してください.

 
#!/usr/bin/sed -nf

# delete all (leading) blanks
/./!d

# get here: so there is a non empty
:x
# print it
p
# get next
n
# got chars? print it again, etc... 
/./bx

# no, don't have chars: got an empty line
:z
# get next, if last line we finish here so no trailing
# empty lines are written
n
# also empty? then ignore it, and get next... this will
# remove ALL empty lines
/./!bz

# all empty lines were deleted/ignored, but we have a non empty.  As
# what we want to do is to squeeze, insert a blank line artificially
i\

bx


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Akihiro Sagawa on January, 21 2003 using texi2html