bashで行の追加について
bashは行指向のため、
行の追加は難しい。
前置きが長くなるが、
まずは、edコマンドの説明から入る。
edの使い方なんて分からなくて良いという人も
行の追加は難しい。
前置きが長くなるが、
まずは、edコマンドの説明から入る。
edの使い方なんて分からなくて良いという人も
いるかもしれないが、
sed,vi,grepの役割を理解する上で重要、
かつ、今でも実践的なので、
ここに書く。
と指定すると、
ファイルを開いて、操作できるようになる。
1pとすると
一行目が表示されるし、
2dとすると
二行目が削除される。
wでファイルを書き込むことになる。
コマンド、アドレスの指定の仕方は
以下のサイトに描かれているので参考にしてほしい。
http://www.uetyi.com/server-const/entry-271.html
SunOSのedのレファレンス
https://docs.oracle.com/cd/E19253-01/819-1210/ed-1/index.html
つまり、sedと違って、
ファイルを直接編集するコマンドである。
edのコマンドのうち
今でも重要なのは
挿入に関する
i 指定の行、文字列の前に挿入する
a 指定の行、文字列の後に挿入する
である。
なぜこのコマンドが重要かというと
まず、
壊れても良いファイルを用意して、
linuxで下のコマンド
を実行してほしい。
$ cat ファイル | sed '1i hellosed'
catせずにできるのでは?
当たり前だが、
元のファイルの一番上の行にhellosedと書かれた
文章が表示される。
また、
$ sed -i '1i hellosed' ファイル
とすると
ファイルが上書きされる。
ここまで来てこれは当たり前のことだと思うかもしれないが、
今度はBSD系統のOS(FreeBSD,OpenBSD等)でこれを実行してほしい。
(macを持っている人はmacでよい。)
おそらく実行できないはずだ。
なぜBSD系統だと実行できないか。
これは、BSDがLinuxに対して劣っているわけでなく、
sedはもともとedの置換の機能に特化したもので、
そもそも文字列を挿入するためのものでないからである。
他にもgrepはedのグローバル(g)で正規表現(re)を表示(p)するためものという
役割なので、grepはあのような動きを取るようにしている。
ここら辺の歴史的な経緯、
名前の由来は
sed&awk プログラミング改訂版
出版社 オライリージャパン
に描かれている
古い本だけど、
今でも十分通用する内容です。
(awkの部分に関しては、今はpython、ruby,Powershellを使ったほうが
良い部分もあるが)
BSDはこの歴史と、
UNIX思想由来の「ひとつの
コマンドはひとつのことだけうまくやらせる」
というルールを愚直に守っている。
そのため、sedがこのような仕様になっている
話が脱線するが、
コマンドがclassみたいになっていて、
コマンドのオプションがクラスの関数のように
働くのはUNIX思想に反する
ので注意しよう(UNIX思想について
全く気にしないならOK)。
なので、
一行目だけファイルに挿入したものを
表示するなら、下のようになる。
BSD系統のOS
BSD系統のOS
cat << EOF | ed -s csvawk 2>/dev/null
> 1i
> helloed
> .
> ,p
> q
> q
> EOF
1iでファイルの一行目にhelloedと書き込んだあと、
,pでファイル全体を表示し、
qでエディタを終了させている。
cat << EOF
でなくて、
echo -e "1i¥nhelloed¥n.,q¥nq¥nq" | ed -s csvawk 2>/dev/null
でもよい。
-eは改行等特殊文字を展開するコマンド
edの-sオプションはファイルに書き込んだ文字数と
を表示しないようにしている。
/dev/nullで標準エラー出力を捨てているが、
これはファイルに書き込んだものをセーブしないときに
出る「?」というエラーを捨てるために行っている。
上書きを行う場合は下のようになる。
cat << EOF | ed -s csvawk
> 1i
> helloed
> .
> w
> q
> EOF
ファイルに対して挿入、上書きに関するスクリプトで、
BSDでもLinuxでも動かしたい場合は
sed,でなく、edで書いたほうが良いということになる。
ちなみにedは上書き用のコマンド
なので注意すること。
問題は、パイプの上流から、来た行を処理するときだ。
Linuxなら、
sed '行番号s 追加したい文字列'
で処理できるが、
BSDなら、
awk '{
if(NR == 指定の行){
print "追加したい文字列"
}
print $0
}'
とする必要がある。
LinuxでもBSDでも動かすには、
後者の書き方で統一する必要があるが、
これはあまりにも不格好だ。
なので、筆者はコマンドを自作することを考えた。
GO言語で書くので、
golangをあらかじめインストールしてほしい。
$ mkdir -p ~/mysrc/golang/insertrow/
として、
~/mysrc/golang/insertrow/
に作業用フォルダを作成し、
そこに
insertrow.goファイルを作成
とする必要がある。
LinuxでもBSDでも動かすには、
後者の書き方で統一する必要があるが、
これはあまりにも不格好だ。
なので、筆者はコマンドを自作することを考えた。
GO言語で書くので、
golangをあらかじめインストールしてほしい。
$ mkdir -p ~/mysrc/golang/insertrow/
として、
~/mysrc/golang/insertrow/
に作業用フォルダを作成し、
そこに
insertrow.goファイルを作成
package main
import (
"bufio"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
)
//処理するまえにエラーがわかったら標準入力を捨てて、
//処理途中でエラーがでたら標準入力がそのままで処理する。
//パイプ上流から受け取らなかったらおうむ返しになるのはsedも同じなのでよい。
//最終行から挿入は標準入力 | tac | insert -n | tac //Linuxの場合
//最終行から挿入は標準入力 | tail -r | insert -n | tail -r
var (
inrow = flag.Int("n", 1, "Insert row")
)
//標準入力を標準エラー入力にリダイレクト
func main() {
//コマンドラインから持ってきたオプションのパース処理。これがないと正しく動かない。
flag.Parse()
if *inrow < 1 {
fmt.Fprintln(os.Stderr, "-nオプションには1以上の数字を入力してください。")
//golangはioutil.Discardを使って捨てることになる。
io.Copy(ioutil.Discard, os.Stdin)
os.Exit(1)
}
//フラグ処理をした後の最初の引数が入る。引数がない場合は空の文字列が入る。
mes := flag.Arg(0)
cin := bufio.NewReader(os.Stdin)
var row []byte
var err error
//行を挿入したかどうかに必要
isInserted := false
row, _, err = cin.ReadLine()
i := 1
for ; err != io.EOF; row, _, err = cin.ReadLine() {
if err != nil && err != io.EOF {
fmt.Fprintln(os.Stderr, "正しく読み込めない行があります。")
}
if i == *inrow {
fmt.Fprintln(os.Stdout, mes)
isInserted = true
}
fmt.Fprintln(os.Stdout, string(row))
i++
}
//標準入力を最後まで読んで指定された行数の方が多い場合はエラーになる。
if *inrow > i {
fmt.Fprintln(os.Stderr, "標準入力に入っている行数より多い行数を指定しました。")
os.Exit(1)
//標準入力の最後の行に追加する場合は下になる。
} else if !isInserted && err == io.EOF {
fmt.Fprintln(os.Stdout, mes)
isInserted = true
}
}
~/mysrc/golang/insertrow
に移動して
$ go build insertrow.go
コンパイルして、
バイナリファイルのinsertrowを作成
~/.bashrcに
~/binへの
パスを通して、
$cp ~/mysrc/golang/insertrow/insertrow ~/bin/insertrow
としてコマンドから実行できるようにする。
使い方
-nオプションで
行数指定。
指定しなければ、デフォルトでは一行目に挿入する
また、
文字列を渡すと、挿入する行に文字列を追加する。
文字列を指定しなければ空文字を入れる。
例
$ ls -1 / | insertrow -n 2
二行目に文字列が空白の行が挿入される。
$ls -1 / | insertrow "hello"
バイナリファイルのinsertrowを作成
~/.bashrcに
~/binへの
パスを通して、
$cp ~/mysrc/golang/insertrow/insertrow ~/bin/insertrow
としてコマンドから実行できるようにする。
使い方
-nオプションで
行数指定。
指定しなければ、デフォルトでは一行目に挿入する
また、
文字列を渡すと、挿入する行に文字列を追加する。
文字列を指定しなければ空文字を入れる。
例
$ ls -1 / | insertrow -n 2
二行目に文字列が空白の行が挿入される。
$ls -1 / | insertrow "hello"
一行目にhelloが追加される。
下の行から2番目に追加
(BSDの場合。Linuxの場合はtail -r の代わりにtacを使う)
$ ls -1 / | tail -r | insertrow -n 2 "hello" | tail -r
まとめ
下の行から2番目に追加
(BSDの場合。Linuxの場合はtail -r の代わりにtacを使う)
$ ls -1 / | tail -r | insertrow -n 2 "hello" | tail -r
まとめ
ファイルの一部に挿入して、表示,上書きは
Linuxだけならsed。
edを使うとBSD,Linuxでも動くようになる。
パイプの上流から来たものに関しては
Linuxだけならsed。
awkを使うとBSD,Linuxともに動くようになる。
しかし、不格好なので、コマンドを
自作することも考える。
また、edコマンドは
数少ない上書き用のコマンドのため、
覚えておくこと。
これがないと、いちいち一時ファイルを作成して、
それをcpコマンドで上書きする
みたいなコマンドを書くしかなくなる。
自作することも考える。
また、edコマンドは
数少ない上書き用のコマンドのため、
覚えておくこと。
これがないと、いちいち一時ファイルを作成して、
それをcpコマンドで上書きする
みたいなコマンドを書くしかなくなる。
コメント
コメントを投稿