Closed10

bash に入門する。

ymgnymgn

1 章

複数コマンドを一度に実行する

ls ; pwd という風に、複数のコマンドをセミコロンで区切ることで、それらをシェルから実行することができる。

Hello World した

bash で Hello World! した。

#!/bin/bash

echo "Hello world"
exit 0

#!/bin/bash は指板と呼ばれるもの。シェルとして bash を使うことをシステムに伝えている。
シバンを書かなかった場合は、現在のシェルの中でコマンドが実行される。

``exit 0` はスクリプトを終了するために使われる。
終了ステータスを引数として指定し、0 以外はスクリプト実行において何らかのエラーが起こったことを表す。

スクリプトの実行は bash /path/to/script.sh
さらに使いがってをよくするなら、スクリプトがパスが通っているディレクトリ以下にある前提で、

chmod +x /path/to/script.sh
script.sh

終了ステータスをチェック

# コマンド1 が何らかの理由で失敗した場合にのみ、コマンド 2 を実行する
command1 || command2

# コマンド 1 が成功して 0 の終了ステータスを発行した場合にのみコマンド 2 を実行する
command1 && command2

$? 変数を参照すると、スクリプトの終了ステータスを明示的に読み取ることができる。

hello1.sh && echo $?
# -> Hello World
# -> 0

引用符の違い

  • 一重引用符(' ') は $ は展開されない(echo 'Hello $1'Hello $1 と表示される)
  • 二重引用符(" ")は $ を展開する(echo "Hello $1"Hello YourName のように $1 の中身が表示される)

スクリプト名の表示

# スクリプト名を表示する
echo "$0" # -> /home/yourName/bin/hello.sh

# パスを表示せずにスクリプトの名前だけを表示する
echo "$(basename $0)" # -> hello.sh

変数・配列の宣言

# 変数宣言
name="Mokhtar"
age=35

# 配列
myarr=(one two three four five)
echo ${myarr[1]} # -> two が表示される(bash は 0-indexed)
echo ${myarr[*]} # -> one two three four five
echo ${myarr} # -> one two three four five

別ファイルから変数を参照する

別ファイルから別ファイルの変数を参照するにはexport を使う。

# script1.sh
export name="ymgn"
./script2.sh

# script2.sh
echo $name

コマンド置換

あるコマンドの実行結果を変数内に補完すること。
コマンド置換を行う方法は 2 種類ある。

  • バッククォート文字(`)
  • $() で囲む
# バッククォート文字で囲む
cur_dir=`pwd`
echo $cur_dir

# $() で囲む
cur_dir=$(pwd)
echo $cur_dir

スクリプトのデバッグ

bash -v /path/to/script.sh
bash -x /path/to/script.sh

他に視覚的にデバッグできる拡張として、Bash Debug という拡張機能があるらしい。
VS Code にはあるそうだけど、Neovim にもあるのなら入れておきたい。

ymgnymgn

2 章

read コマンドでユーザ入力を受け付ける

ユーザに入力を促すスクリプトを実装するときは、末尾改行を取り除いた echo と、read を組み合わせると良い。

#!/bin/bash
echo -n "Hello $(basename $0)! May I ask your name: "
read
echo "Hello $REPLY"
exit 0

これと同じような処理は read -p を使うことでも実現できる。

#!/bin/bash
read -p "May I ask your name: " name
echo "Hello $name"
exit 0

read-n数字 オプションを付与すると、入力文字数を制限することができる。

#!/bin/bash
read -p "May I ask your name: " name
echo "Hello $name"
read -n1 "Press any key to exit"
echo
exit 0

read-s (サイレント)オプションを付与すると、入力文字を不可視にできる。

オプションとパラメータの受け渡し

オプション・・・ -a のように、 - から始まる文字のこと。
パラメータ・・・ ただの引数

オプションとパラメータを同時に渡すには、二重ハイフン(--)で区切る必要がある。

./script1.sh -a -b -c -- p1 p2 p3

ファイルの読み取り

read はファイルを読み取ることにも使うことができる。

#!/bin/bash
while read line
do
  echo $line
done < yourfile.txt

オプションを実装する際は標準的であることが望ましい

オプション 説明
-a 全ての項目をリスト表示
-c 全ての項目数を取得
-d 出力ディレクトリを指定
-e 項目を展開
-f ファイルを指定
-h ヘルプページを表示
-i 大文字小文字を区別しない
-l テキストをリスト表示
-o 出力をファイルに送る
-q サイレントモード
-r 再帰的に処理する
-s ステルスモード
-v 冗長(詳細)モード
-x 実行可能ファイルを指定
-y ユーザにプロンプトを表示せず受諾
ymgnymgn

3 章

決定経路

コマンドラインリスト・・・ && または || で 2 つ以上の分を結合する記法。
A && BA が成功した場合にのみ B も実行され、A || BA が失敗した場合にのみ実行される。

echo $? を使うことで、終了ステータスを読み取ることができる。

test コマンド

常に 0 または 1 の終了ステータスを返す。

test EXPRESSION

# ! を使用して評価を反転させる書き方
test ! EXPRESSION

# 複数の式を評価に含める場合は -a (AND) または -o (OR) オプションを付ける
test EXPRESSION1 -a EXPRESSION
test EXPRESSION -o EXPRESSION

この EXPRESSION には評価すべき式を表す。
また、式を囲む角括弧 ( [] ) に置き換えることでも test と同じ結果が得られる。

[EXPRESSION]

文字列のテスト

root ユーザかどうかを判定

test "$USER" = root

# 角括弧を使う場合
[ "$USER" = root ]

root でないかを判定

test ! "$USER" = root

# 角括弧を使う場合
[ ! "$USER" = root ]

整数のテスト

"$#" はスクリプトに渡されたパラメータ数を取得できる。

test "$#" -gt 0
[ "$#" -gt 0 ]

if 文

他のプログラミング言語と同様に if - elif - else が使える。

if [ "$#" -lt 1 ] ; then
  echo "Usage: $0 <name>"
  exit 1
fi
echo "Hello $1"
exit 0

case 文

casein
  ケース 1)12
  ;;
  ケース2)
    文1
    文2
  ;;
  *)
    文1
  ;;
esac

80/20 ルール

全体の時間のうち 20 % はメインスクリプトを書くことに費やされ、80% は起こりうる全ての事態がスクリプト内で正しく扱われていることを確認することに費やされる。

ymgnymgn

4 章

特に書くことがなかったので省略。
出力カラーを指定できることが便利なくらい。

ymgnymgn

5 章

パラメータ置換

${parameter-default} という書き方をすることで、parameter が null の場合に default の値が使われるようにデフォルト値指定できる。

name=${1-"Anonymous"}

引用符を付けておくと安全

FILE="my file"
rm $FILE # -> rm my file と解釈され、my というファイルと file というファイルを削除するコマンドになってしまう

rm "$FILE" # rm "my file" と解釈され安全

[[ を使った高度なテスト

[[ ... ]] を使うと、[ ... ] よりも高度な記法が使える。

  • &&, || 演算子
  • 正規表現によるパターンマッチング
FILE="my file"

# [[ ... ]] の中では && や || が使え、さらに引用符(" ")を省略可能
[[ -f $FILE && -r $FILE ]] && cat "$FILE"

# 正規表現
[[ $FILE =~ \.pl$ ]] && cp "$FILE" scripts/

(( を使った算術演算

(( ... )) は算術展開を可能にする。
C 言語スタイルのインクリメントも使うことができる。

a=$(( 2 + 3 ))
echo "$a" # -> 5

count=1
(( count++ ))
echo "$count" # -> 2

算術テスト

(( ... )) を使うことで、他のプログラミング言語に似た比較演算子を使うことができる。

count=10
(( count-- ))
(( count > 1 )) && echo "Count is greater than 1"

# if 文と組み合わせることもできる
if (( count > 1 )) ; then
  echo "Count is greater than 1"
fi
ymgnymgn

6 章

for ループ

for u in bob joe ; do
  echo "$u"
done

IFS (内部フィールドセパレータ)

IFS は区切り文字のこと。デフォルトではスペースとタブと改行の 3 つ。

こういう内容のファイルがあったとして、1 行ずつ読み取りたいとき、

# file1.txt

Hello, this is a test
This is the second line
And this is the last line

このループを実行すると、求めたい結果とは異なってしまう。

#!/bin/bash
file="file1.txt"
for var in $(cat "$file")
do
  echo "$var"
done

# 実行結果
Helo,
this
is
a
...

IFS を改行のみに変更すると行ごとのループになる。

#!/bin/bash
file="file1.txt"
IFS=$"\n" # IFS を改行のみにする
for var in $(cat "$file")
do
  echo "$var"
done

ファイルとディレクトリのチェック例

#!/bin/bash

for path in /home/*
do
  if [ -d "$path" ] ; then
    echo "$path is a directory"
  elif [ -f "$path" ] ; then
    echo "$path is a file"
  fi
done

C 言語ライクな for 文

for (( v=1; v <= 10; v++ ))
do
  echo "value is $v"
done

break, continue を使ったループ処理制御

for f in * ; do
  [ -d "$f" ] || continue
  chmod 3777 "$f"
done
for f in * ; do
  [ -d "$f" ] && break 
done
echo "We have found a directory: $f"

while ループと until ループ

使う頻度低いと思うので割愛。

余談

https://ez-net.jp/article/FC/2HHU3CBD/_LiO_9u5MU5x/ より

標準出力だけを捨てれば良いのであれば、次のようにして、標準出力の出力先を、画面から "/dev/null" に切り替えてあげます。

find *.h > /dev/null

さて、これだけだと何らかのエラーで標準エラーに結果が出力された場合、それは画面に表示されてしまいます。

標準エラーの内容も画面に表示させないためには、標準エラーを標準出力に回すという意味の "2>&1" という記号を後ろに付けます。

find *.h > /dev/null 2>&1

ymgnymgn

7 章

関数の作成

function-name() {
  <code to execute>
}

# 例
show_system() {
  echo "The uptime is:"
  uptime
  echo
  echo "CPU Detail"
  lscpu
  echo
  echo "User list"
  who
}

関数へのパラメータ渡し

関数名 パラメータ とスペース区切りで書くことで、関数にパラメータを渡すことができる。
パラメータへは、関数内で $1 とすることでアクセスすることができる。

配列の引き渡し

配列をパラメータとして受け取るには $@ を使う。
$1 にすると、最初の要素だけが取得されるので注意。

myfunc() {
  arr=$@
  echo "The array from inside the function: ${arr[*]}"
}

test_arr\(1 2 3)
echo "The original array is: ${test_arr[*]}"
myfunc ${test_arr[*]}

変数のスコープ

シェルスクリプトの変数は全てグローバル変数になるため、関数内だけに限られる変数を宣言したい場合は local を付与すると良い。

myfunc() {
  local myvar=10
}

関数から値を返す

2 通りある。

  1. return コマンドで終了ステータス(0 ~ 255)を返す方法
  2. echo コマンドで標準出力に出力する方法

return コマンドで返す場合、関数の呼び出し側は $? で終了ステータスを受け取ることができる。
echo コマンドで返す場合、関数の呼び出し側はコマンド置換($(...))で返り値を受け取ることができる。

ymgnymgn

8 章

sed コマンドはストリームエディタと呼ばれ、ファイルを 1 行ずつ読み込み、その内容を検索または編集する。

grep を使ってテキストを表示する

grep はファイル内の全ての行を検索を行い、結果を標準出力 STDOUT に出力する。

| (パイプ)を使うと、ifconfig の出力を grep の入力に送ることができる。
この例では、rx packets というシンプルな文字列を検索している。
-i を付けると、大文字小文字を区別しないようにできる。

ifconfig eth0 | grep -i "rx packets"
ymgnymgn

得たかった知識より高度な内容が多く、これ以上読み進めるのは今は必要ないと判断して中止。

このスクラップは2023/07/17にクローズされました