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

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

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

Some exotic examples: 4.1 行の中央揃え 


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 newline and 80 spaces to end of line
G

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

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

4.2 数字を増加させる

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

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

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

 
#!/usr/bin/sed -f

/[^0-9]/ d

# replace all leading 9s by _ (any other character 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 [-h] [-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 is no path, only a 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\n, 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; }
/ () $/! { 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の出力を反転させることです(8)

 
#!/usr/bin/sed -f

/../! b

# Reverse a line.  Begin embedding the line between two newlines
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 newline markers
s/\n//g

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

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

GNU sedとGNU 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 newline 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コマンドではなく,ループを用いて行なっています(9).最初 に一の位を変換し,`a'を数値から削除します.そして十の位が`a' になるように文字を回転させ,残っている文字が無くなるまでそれを続けます.

 
#!/usr/bin/sed -nf

# Add n+1 a's to hold space (+1 is for the newline)
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 October, 5 2005 using texi2html 1.70.