それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

bashの引数の扱い方(関数の引数・位置パラメータ)についての基本をまとめ

位置パラメータについて。

bashの使い方 のシリーズ。今回は、位置変数(位置パラメータ)について見ていきたいと思います。

引数をオプションとして使いたい人は。→ getopts の記事を参考にしてください

ここでは、bash の引数へ配列としてアクセスして関数やスクリプトでどう扱うかを考えていきます。

引数のことは、positinal parameter ・位置パラメタとして扱われます。

引数:位置パラメータとは

位置パラメータは、bashには positional parameter と書かれています。位置変数・位置パラメータ・ポジショナルパラメータなどと呼ぶことが有ります。

位置パラメータを一言で言えば、「引数を格納した配列」のことです。

位置パラメータは配列

配列だけど、ちょっと特殊な配列で、名前がありません。無名な配列です。

関数の引数やシェルスクリプトの引数に使われるので、いちいち名前をつけてないということらしいです。

位置パラメータのサンプル(シェルスクリプト

位置パラメータをどのように使うのか少し見ておきたいとお思います。

位置パラメータを扱う例
#!/usr/bin/env bash 

echo $0 
echo $1
echo $2 
echo $3
上記を実行した結果
$ bash sample01.sh a1 a2 a3
sample01.sh
a1 
a2
a3

位置パラメータへのアクセス

位置パラメータは名前のない配列なので、配列名前の箇所を省略したとてもかんたんな書き方ができてることがポイントです。

変数 意味
$0 実行中のファイル名
$1 1番めの変数
$2 2番めの変数
$N N番めの変数(0-9)だけ
${N} N番めの変数、10番以降は必須
$@ $1..$N までの引数配列すべて
$* $@と同じ。ただしIFS区切り
$# 配列個数:つまり引数の個数
${@: X: n} x番目からN個のスライス

ただし、10番目以降は $10$1 0 と解釈されてしまうので、${10} のようにブレイスで囲う必要があります。

また、$@ の配列は $1..$N までで、$0 は含まれません。

もちろん配列なのでスライスで部分配列を切り出す事もできます。

関数・シェルスクリプトの引数の違い

関数とシェルスクリプトでは、引数の扱いはほぼ同じですが、1点だけ注目すべき違いが有ります。それは$0の違いです。

関数の例 sample02.sh
function my_func(){
 echo $FUNCNAME
 echo $0
}
sample02
sample02.sh の実行結果
$ bash sample02.sh 
my_func
sample02.sh
関数とシェルスクリプトの違い

関数中でも、$0 は 関数の書かれているファイル名です。 シェルスクリプトでは、$0 は自分自身のファイル名になります。

もし関数中で、自分の名前が必要なときは $FUNCNAME を参照します。

あと、interactive shell (RPEL)で実行中は bash になります。

位置変数は1から始まる、いちだけに。と覚えておくと便利なのかもしれません。

シェルで実行していた場合
$ function sample02(){
>  echo $FUNCNAME
>  echo $0
> }
$ sample02
sample02
-bash

名前がありませんが、名前はあります。

位置パラメータには名前がありませんが、わかりやすいように名前をつけてアクセスすることも出来ます。

それが、次の場合の arg です。

自分で別名の変数に保存しておくことも出来ます。

位置パラメータをループする

位置パラメータだけに許された記法があります。それがとても簡便な方法です。

位置パラメータ特有のfor
for arg; do
    echo $arg
done

とりあえず、こうやって簡便にARGVを全部回せる方法があることを知っておくといいと思います。

これの仕組みは、省略記法です。argは単なる変数名で、変数名はなんでも大丈夫です。

for i ; do 
    echo $i;
done

もちろん for .. infor (( i=1;i<max;i++)) ;でループするのもいいと思います。*1

引数をすべて、なくなるまで処理

ファイル名を複数個で引数として受取り、それを逐次処理するような場合には上記のようなループ構造が便利です。 forでは、どこまで進んだかわからなくなる。そのため引数をキュー的に使えたほうが便利です。

古くからの先人は shiftを使って、処理することがあるようです。

while ループであるだけ回す
while [ "$1" ]
do
    echo "$1"
    shift
done

これは、$1 が先頭なのを利用しています、$1 を使い終わったら shift$@を進めています。 shift は引数(位置パラメータ)を処理すると暗黙的に決められています。

上のコードを一つずつ見ていくと次のとおりです。

  • while [ "$1" ] 終了条件 。$1 が空でないとき
  • echo $1 $1 を出力する
  • shift 引数を一つすすめる

わたしは、shiftshift $@ のの意味だと思うようしています。

位置パラメータを使うとループがシンプルになる。

while : ; do shiftfor arg のように 位置パラメータ特有の記述方法をすることでよくある処理が手早く終わらせるられるようになっています。

bashシェルスクリプトと関数はいつも「不定個」の引数を取ることになっています。そのため、ループはあるだけ回すのが基本になっています。他のプログラミング言語が関数を一種の型のように扱い、引数と戻り値を厳密に決めていることとは大きく異なります。

位置パラメータを自分で作れないの?

このような便利な位置パラメータですが、自分で作れないのでしょうか。

配列を処理するときに、位置パラメータを使えると便利そうです。これは出来ます。

set を使います。

set を使えば、位置パラメータをその場で好きな配列(文字列)に上書きすることが出来ます。

set の使用例
set I like youtube.
set "he hates youtube." # クオートも

set で囲むことで、囲った部分を配列に展開し、positional parameter に展開することが出来ます。

位置パラメータを自分で使う サンプル
set I like youtube.
for arg; do
    echo $arg
done
サンプルの実行結果
takuya@Desktop$ set I like youtube.
takuya@Desktop$ for arg; do
>     echo $arg
> done
I
like
youtube.

set で位置パラメータを使うときの注意

set をしてしまうと、スクリプト、関数のもともとの引数は上書きされます。

なぜならその空間で使える位置パラメータは1つだからです。これは名無し変数なので仕方のないことです。

set が分かれば、function の引数が理解できる。

set の使い方がわかったので、関数と見比べ見ると。

関数の引数はその空間でset しているだけだと理解できそうです。

function sample () {
   # ここはset済みしか見えない
}
sample a1 a2 a3 # ここで 引数string "a1 a2 a3"をset している

引数を解析する

引数は配列で、ちょっと特殊な配列で、位置パラメータということがわかりました。

ここで覚えたことを使っていけば、かんたんな引数処理はすぐ書くことが出来ます。

引数があるときは引数を、ないときはSTDINから読む

引数で渡したり、コマンドから渡したり、両方に対応できるシェルスクリプトも作れますね。

# STDINか引数かどちらも同じに使える例
if (( $# >  0   )); then 
   echo $@
else
  echo $( cat - )
fi
上記の実行例。
takuya@Desktop$ bash  test.sh
abc
CTRL+D
abc
takuya@Desktop$ bash  test.sh  aaa
aaa

cat の入力は CTRL+D ( EOF ) で入力終了できるので、実際入力するときはCTRL+Dで入力を終了してください。

引数の処理を使ってコマンドオプションを処理する

-a-b のように指定したオプションが来たときだけ処理をしてそれ以外を捨てることも出来ます。

while :
do
    case "$1" in
      -f | --file)
      file="$2"   # You may want to check validity of $2
      shift 2
      ;;
      -*)
      echo "Error: Unknown option: $1" >&2
      exit 1
      ;;
      *)  # No more options
      break
      ;;
    esac
done

ここで while ::何もしないtrueを返すコマンドです。なので無限ループです。内部でshift や exit /brek をしているので、引数があるだけループします。

つまり、このスクリプトは、以下のような処理をすることが出来るようになります。

sample -f a.txt -f b.txt  -f c.txt

オプションの解析は getopts

もっと細かいオプションの解析をするには getopts が使えます。

getopts はシェル組込み関数でいつでも使えます。ヘルプは help getopts で見ることが出来ます。

私も過去に紹介しているので、そちらも参考にしてください

ちなみに、getopt は /usr/bin/getoptです。

2016-12-28

if の条件が、文字列比較になってたので、数値比較に直した

関連資料

takuya-1st.hatenablog.jp

参考資料

*1: 補足ですが for e in $@ で回したときも $0 は出てきません