スクリプトを書く

INPUT · スライド

関数で名前を付ける

01 / 08

長くなったら、名前で分ける

前の回で書いたスクリプトは、だんだん長くなってきたね。

set -eutrap … EXIT[ $# -eq 0 ] && { echo …; exit 2; }grep -c … > …KAZU=$(…)if [ … ]; then … fi

7行でも「どこが何をしているか」を読むのに時間がかかるでしょう。50行になったらもっとだね。

そこで手順のかたまりに名前を付けるんだ。

aisatsu() {  echo "konnichiwa $1"}aisatsu taroaisatsu hanako
konnichiwa tarokonnichiwa hanako

これが関数だよ。

2つの値打ちがあるんだ。

1. 同じ手順を、何度も呼べる2. 名前が付くので、読んで分かる

2番が意外と大きいよ。

tsukaikata_wo_dasu     見ただけで分かるgrep -c … | awk …      読まないと分からない

名前が説明になるでしょう。コメントを書かなくても伝わるんだ。

02 / 08

書き方と呼び方

形はこれだよ。

namae() {  やること}

呼ぶときは名前だけだね。

namae

丸かっこは書かないよ。ここが他の言葉と違うところなんだ。

namae()      定義するとき(かっこを書く)namae        呼ぶとき(かっこは書かない)

他の言葉では namae() と呼ぶでしょう。シェルでは違うんだ。理由はこうだよ。

シェルでは、関数もコマンドと同じ扱い
ls a.txt        コマンドを呼ぶnamae a.txt     関数を呼ぶ

見た目が同じでしょう。だから使う側は、関数かコマンドかを気にしなくていいんだ。

確かめてみると分かるよ。

~ $ type aisatsuaisatsu is a function~ $ type lsls is /bin/ls

章10でやった type だね。あのとき「エイリアス / 内蔵 / ファイル」の3つを見たでしょう。関数も仲間だったんだ。

1つ大事な決まりがあるよ。

> 関数は、呼ぶ前に定義する

aisatsu          ← ここではまだ知らないaisatsu() { … }

これは動かないんだ。上から読んでいるからだね。だから関数はスクリプトの上のほうにまとめて書くよ。

03 / 08

$1 が切りかわる

ここがこの回でいちばん大事なところだよ。

#!/bin/shf() {  echo "kansuu no 1: $1"}f naka
~ $ ./a.sh sotokansuu no 1: naka

naka が出たでしょう。soto ではないんだ。

./a.sh soto      スクリプトの $1 は sotof naka           関数の中の $1 は naka

関数の中では、関数に渡したものに切りかわるんだよ。

$#$@ も同じだね。

#!/bin/shf() { echo "naka=$#"; }f a b cecho "soto=$#"
~ $ ./g.sh x ynaka=3soto=2

別々に数えているでしょう。

これは便利な性質だよ。関数が小さなスクリプトのように書けるからね。

shiraberu() {  [ -f "$1" ] || return 1  grep -c . "$1"}shiraberu aka.txtshiraberu ao.txt

2本目で覚えた $1 の書き方が、そのまま使えたでしょう。

注意点は1つだよ。関数の中でスクリプトの引数を使いたいときは、渡してあげるんだ。

f "$1"           スクリプトの $1 を関数に渡すf "$@"           全部渡す

自動では見えないということだね。

04 / 08

return で成否を返す

関数も終了ステータスを返せるよ。

shiraberu() {  [ -f "$1" ] || return 1  grep -c . "$1"}
~ $ shiraberu aru.txt; echo $?30~ $ shiraberu nai.txt; echo $?1

return 1 で失敗を返したね。前の回でやった exit 1 と似ているでしょう。

でも違いが大きいんだ。

return 1     関数だけ終わる(スクリプトは続く)exit 1       スクリプトごと終わる

これは大事な区別だよ。

for F in *.txt; do  shiraberu "$F" || echo "$F wa dame"done

1つ失敗しても次のファイルへ進めるでしょう。exit にしてしまうと、そこで全部終わってしまうんだ。

だから作法はこうだね。

関数の中    return を使う本体の流れ  exit を使う

そして関数も、&& でつなげられるよ。

shiraberu aka.txt && echo "ok"shiraberu nai.txt || echo "ng"

コマンドと同じように扱えるでしょう。前の回でやった「成否を返す道具は、つなげられる」が、関数でもそのまま効くんだ。

return を書かないときは、最後の行の成否が返るよ。これも exit と同じ決まりだね。

05 / 08

変数は共有される

関数の中で変数を変えると、外にも効いてしまうんだ。

#!/bin/shV=sotokaeru() { V=naka; }kaeruecho $V
naka

外の V が変わってしまったね。他の言葉に慣れている人は驚くところだよ。

ふつうの言葉   関数の中の変数は、中だけのものシェル         全部つながっている

これは便利なこともあるけれど、事故のもとでもあるんだ。

f() { I=0; while … done; }     f の中で I を使うfor I in 1 2 3; do f; done     外でも I を使っている!

気づかないうちに壊れるでしょう。

分けたいときは local を使うよ。

f() {  local X=naka  echo $X}

local を付けた変数は、関数の中だけのものになるんだ。

作法を決めておくといいよ。

> 関数の中で使う変数には local を付ける

shiraberu() {  local F="$1"  local KAZU  KAZU=$(grep -c . "$F")  echo "$KAZU"}

こうしておけば、外の変数とぶつからないでしょう。

1つだけ注意すると、local は POSIX で決まっているものではないんだ。でも ash bash zsh のどれでも動くので、実用上は使ってよいよ。

06 / 08

出力で持ち帰る

関数から値をもらう方法は2つあるよ。

1. 出力で返す

kazu() { echo 42; }N=$(kazu)echo "got=$N"
got=42

章10で覚えた $(…) で受け取るんだ。

2. 変数に入れる

kazu() { KEKKA=42; }kazuecho "got=$KEKKA"

変数が共有だから、これでも動くね。

どちらを使うかというと、出力のほうがきれいだよ。

出力で返す    どこから呼んでも同じに動く変数で返す    名前がぶつかると壊れる

そして出力で返すと、パイプにも流せるでしょう。

kazu | wc -c

コマンドと同じ形になるんだ。

1つ気をつけることがあるよ。

shiraberu() {  echo "shirabete imasu…"      ← これも出力!  grep -c . "$1"}N=$(shiraberu aka.txt)

N2行とも入ってしまうね。だから途中の知らせは標準エラーへ出すんだ。

  echo "shirabete imasu…" >&2

章4でやった >&2 だね。

1(標準出力)   結果を返す道2(標準エラー)  人に見せる道

道を分けてあった意味が、ここで分かるでしょう。

07 / 08

エイリアスとの使い分け

章10でエイリアスをやったね。関数と似ているでしょう。

alias ll='ls -l'll() { ls -l "$@"; }

どちらも ll で呼べるよ。違いを整理しておこうね。

エイリアス関数
書ける長さ1行だけ何行でも
引数うしろに付くだけ$1 で好きな場所に置ける
条件・繰り返し書けない書ける
スクリプトの中効かないことが多い効く

引数の扱いがいちばん大きな差だよ。

alias mkcd='mkdir -p'        場所を指定できないmkcd() {  mkdir -p "$1" && cd "$1"    2回使える!}

エイリアスでは $1 を2回使えないでしょう。うしろに付くだけだからね。

使い分けの目安はこうだよ。

短い言いかえ        →  alias引数を使いたい      →  関数条件や繰り返しが要る  →  関数

章10で「深くしたくなったらスクリプトにする合図」と言ったでしょう。実はその中間が関数なんだ。

alias      1行の言いかえ関数       .profile に置ける、手順のかたまりスクリプト  人に渡せる、独立した道具

3段階あるんだね。~/.profile に関数を書いておけば、端末を開くたびに使える自分の道具になるよ。

08 / 08

さあ、打ってみよう(章11の締め)

この回で使う形をまとめておくね。

namae() { … }        定義(呼ぶ前に書く)namae hikisuu        呼ぶ(かっこは書かない)$1 $# $@             関数に渡したものreturn 1             失敗を返す(関数だけ終わる)local X=…            中だけの変数N=$(namae)           出力を受け取るecho … >&2           知らせは標準エラーへ

そして章11をふり返っておこうね。7本でここまで来たよ。

1本目  #! と実行の印       手順をファイルに書く2本目  引数($1)          相手を選べる3本目  if                  条件で分かれる4本目  for                 たくさんの相手に5本目  while read          行を1行ずつ6本目  終了ステータス       成否をやりとりする7本目  関数                 名前で分ける

プログラムの3要素が全部そろったでしょう。

順に実行する    1本目条件で分かれる  3本目繰り返す        4・5本目

これで書けないことは、ほとんど無いんだ。あとは組み合わせと、読みやすさの話だよ。

次の章12では、書いた道具を見ていないあいだも動かすようになるよ。

cron      決めた時刻に動かすlogger    記録に残すip / ping ネットワークを見るhttpd     自分のページを出す

あなたが作った道具が、自分の手を離れて働くようになるんだ。楽しみでしょう。では最後の1本をやってみよう。