0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Shellの基本を学ぶ(7)関数

Posted at

Function

シェルファイルのようなもの。パラメータを受け取れる。ポジショナルパラメータ $1, $2... も受け取れる。

構文は次の通り

  • function name() {...} # オススメ
  • function name {...}

ポイントとしては、名前の重複に気をつけること。例えばlsという Function を定義すると、もともとあったlsが使えなくなるので注意。

サンプル1

引数も、戻り値も使える

#!/bin/bash

multiply() {
    return $(( $1 * $2 ))
}

multiply 4 5
echo $?

実行1

$ ./funcdemo
20

サンプル2

ただし、この例では、特に戻り値はいらないので、省略してみる。

#!/bin/bash

multiply() {
    echo $(( $1 * $2 ))
}

multiply 4 5

結果は同じ

サンプル3

次は、関数の戻り値がtrue or false で帰って来るので、それで判別している。関数の戻り値は、$? で取得しているのがポイント

#!/bin/bash

starts_with_b () {
    [[ $1 == [bB]* ]];
    return $?
}

result=$(starts_with_b Boy)
echo $?

if starts_with_b Boy; then
  echo "Start with B"
else
  echo "Not start with B"
fi

実行

$ ./funcdemo
0
Start with B

ポイント

Function の中で定義された関数はローカルになる。
戻り値は、Exit ステータスコードにする。return ステートメントがなければ、Functionは最後のステートメントのステータスを返す

戻り値

スクリプトにグローバルな変数を使う
もしくは、Output ストリームに返す

Export Functions

export -f fun 

サンプルの数々

box

入力した文字の周りのボックスを作る。${#1} はパラメータの文字数のカウント

#!/bin/bash

drawline () {
    declare line=""
    declare char="-"
    for (( i=0; i<$1; ++i )); do
      line="${line}${char}"
    done
    printf "%s\n" "$line"
}

[[ ! $1 ]] && exit 0

declare -i len="${#1} + 4"
drawline $len
printf "| %s |\n" "$1"
drawline $len

実行

$ box hello
---------
| hello |
---------

count 改造

ポイントは、ヒアドキュメントの書き方、それから、errorの関数は、エラー出力に吐くようになっている。標準出力もエラー出力に出力される書き方。

一つ謎なのが、getopts の ":hb:s:r" の書き方。こうしないと動かない。最初は見間違いと思ったが。

#!/bin/bash

usage () {
    cat <<END
count [-r] [-b n] [-s n] stop

Print each number up to stop, beginning at 0
   -b: number to begin with (default: 0)
   -h: show this help message
   -r: reverses the count
   -s: sets step size (default: 1)
END
}

error() {
    echo "Error: $1"
    usage 
    exit $2
} >&2

isnum() {
    [[ $1 =~ ^[0-9]+$ ]]
}

declare reverse=""
declare -i start=0
declare -i step=1



while getopts ":hb:s:r" opt; do 
  case $opt in
    r)
      reverse="yes"
      ;;
    h)
      usage 
      exit 0
      ;;
    b)
      isnum ${OPTARG} || error "${OPTARG} is not number" 1
      start="${OPTARG}"
      ;;
    s)
      isnum ${OPTARG} || error "${OPTARG} is not number" 1
      step="${OPTARG}"
      ;;
    :)
      echo "Option -${OPTARG} is missing an argument"
      exit 1
      ;;
    \?)
      echo "Unknown option: -${OPTARG}" >&2
      exit 1
      ;;
  esac
done

shift $(( OPTIND -1 ))

[[ $1 ]] || { echo "Argument is missing" >&2; exit 1; }
declare end="$1"

if [[ ! $reverse ]]; then
  for (( i=start; i <= end; i+=step )); do
    echo $i
  done
else
  for (( i=end; i >= start; i-+step )); do
    echo $if
  done
fi

exit 0

実施結果

$ count -b 5 -s b 100
Error: b is not number
count [-r] [-b n] [-s n] stop

Print each number up to stop, beginning at 0
   -b: number to begin with (default: 0)
   -h: show this help message
   -r: reverses the count
   -s: sets step size (default: 1)

read_pipe

Bash の初心者がやりがちな問題。次のプログラムは想定した通りに動かない。理由は、$* | count_lines の行で、count_lines が実行されるのは、サブプロセスだから、ここで定義された変数の数字をアップデート出来ない。ちなみに、$* はポジショナルパラメーターを展開して初めからスタートしたもの。

#!/bin/bash

declare -i count=0

count_lines () {
    while read -r; do 
      ((++count))
    done
    echo $count
}

$* | count_lines
echo $count

結果

$ read_pipe ls
9
0

hist

ディレクトリのファイルサイズと、パーセンテージを表す。ちなみに、bash の場所が違うのは、このシェルの実行には、V4 が必要で、Macは3xなので、インストールする必要があったため。

#!/usr/local/bin/bash

# This script prints a histrogram of how much space
# directories in the current working directory use

error () {
    echo "Error: $1"
    exit $2
} >&2

# Create a temp file.
my_mktemp () {
    mktemp || mktemp -t hist
} 2> /dev/null

echo $BASH_VERSINFO[0]
(( BASH_VERSINFO[0] < 4 )) && error "Bash 4+ required." 1

declare -A file_sizes
declare -r tempfile=$(my_mktemp) || error "Can not create templfile" 2

declare -ir term_cols=$(tput cols)

declare -i max_name_len=0 max_size=0 total_size=0

drawline () {
    declare line=""
    declare char="-"
    for (( i=0; i<$1; ++i )); do 
      line="${line}${char}"
    done
    printf "%s" "$line"
}

read_filesizes () {
    while read -r size name; do 
      file_sizes["$name"]="$size"
      (( total_size += size ))
      (( max_size < size )) && (( max_size=size ))
      (( max_file_len < ${#name} )) && (( max_file_len=${#name} ))
    done
}

{ du -d 0 */ || du --max-depth 0 *; } 2>/dev/null > "$tempfile"
read_filesizes < "$tempfile"

declare -i length percentage
declare -i cols="term_cols - max_file_len - 10"

echo "file_sizes${!file_sizez[@]}"

for k in "${!file_sizes[@]}"; do
  (( length=cols * file_sizes[$k] / max_size ))
  (( percentage=100 * file_sizes[$k] / total_size ))
  printf "%-${max_file_len}s | %3d%% | %s\n" "$k" "$percentage" $(drawline $length)
done

printf "%d Directories\n" "${#file_sizes[@]}"
printf "Total size: %d blocks\n" "$total_size"

rm "$tempfile"

結果

$ hist
5[0]
file_sizes
variables/ |   5% | -----------
file/      |   5% | -----------
forloop/   |  40% | ----------------------------------------------------------------------------------------
bin/       |  50% | ---------------------------------------------------------------------------------------------------------------
4 Directories
Total size: 160 blocks

まとめ

  • Function の直後にリダイレクトをすることが可能 fun () {...} >&2
  • コマンドのパイプラインは、サブシェルで実行される ls | while read -r; do ((++count)):done 動作しない。ファイルを使う
  • ヒアドキュメントは << Tag ...Tag
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?