シェルスクリプトとは
シェルは通常コマンドを対話的に入力します。人の手で入力すると以下のような問題が出てきます。
- 毎回手入力するとミスの恐れがある
- 連続的な操作の場合は、前のコマンド実行が完了するまで待たなければいけない
- 任意の時間(夜中とか)に実行したいとき、その時間に人が待機していないといけない
実行するコマンドをテキストファイルに記録しておき、まとめてシェルに実行させることができます。
事前に実行したいシェルスクリプトを用意して、cron等を使って任意の時間に実行することもできます。
とりあえず作ってみる
ホームディレクトリにいることを確認してください。
ホームディレクトリに以下の内容を書いたexample1.shを作成してください。
#!/bin/bash
echo 'Hello world!'
作り方
$ vim example1.sh
-
i
キーを押してインサートモードへ - 内容を入力
-
Esc
キーを押してノーマルモードへ -
:
w
q
enter
キーを順に押して、保存し終了する
このままだと実行権限がありません。作成したファイルに実行権限をつけます。
$ ls -l | grep example1.sh
-rw-r--r-- 1 sci02189 sci02189 0 Apr 4 15:41 example1.sh
$ chmod +x example1.sh
$ ls -l | grep example1.sh
-rwxr-xr-x 1 sci02189 sci02189 0 Apr 4 15:41 example1.sh
実行します。
$ ./example1.sh
Hello world!
練習課題
次の出力結果になるようにexample1.shを 編集 してください。(vimの練習も兼ねてます)
$ ./example1.sh
Hello!
My name is ○○
答え(できるだけ見ないで!)
#!/bin/bash
echo 'Hello!'
echo 'My name is ○○'
このように、実行したいシェルのコマンドを順にテキストファイルに書いていくだけです。
シバン
先頭に書いた
#!/bin/bash
のこと。
テキストファイルの先頭行に書くことで、どの処理系に実行させるかを明記します。
#!/usr/bin/python
と書いたらpythonが処理します。
今回の場合はシェルで叩いて実行しているので書かなくてもシェルとして動いてしまいますが、明示するべきです。
書かないのであれば、
/bin/bash ./example1.sh
という風に実行します。(そもそも先頭に書くべきです。)
おまじない set -eu
シェルスクリプトを書くときに、先頭行に
set -eu
ととりあえず書くと良いとされています。
分からないうちはおまじないとして書いておくのが安全です。
-e
シェルスクリプトを上から実行するときに、エラーがあったらその時点で打ち止めにするオプションです。
1行目でデータを作成できた前提で2行目を書いたのに、1行目でエラーが起きて2行目が想定してない状況で動いてしまう……というのを防ぐことができます。
-u
未定義の変数を使おうとしたときに打ち止めにするオプションです。
変数に入れた名前のディレクトリを消すスクリプトで
rm -rf /${DIR_NAME}
としたときに、うっかり変数を未定義で動かし悲惨な事になるのを防ぐことができます。
しかし、-eオプションについては
command1 || command2
のようなエラー処理が書けなくなります。
途中でエラー処理を書く場合は
#!/bin/bash
set -eu
~さまざまな処理~
set +e
command1 || command2
set -e
~さまざまな処理~
のようにすると書くことができます。
※これができる段階であればエラーが起こりうるタイミングを把握できているので、もう-eを書かなくても大丈夫な理解レベルに達している説はあります。
変数
シェルスクリプトでは変数を使う事が可能です。
ホームディレクトリに以下の内容を書いたexample2.shを作成してください。
#!/bin/bash
var1='Hello'
var2='World!'
echo ${var1} ${var2}
実行します。(実行権限をつけるのを忘れずに!)
$ ./example2.sh
Hello world!
配列も使えます。
ホームディレクトリに以下の内容を書いたexample3.shを作成してください。
#!/bin/bash
names=("John" "Jane" "Bob" "Alice")
echo ${names[2]}
実行します。(実行権限!)
$ ./example3.sh
Bob
シェルの実行結果を変数に格納することも可能です。
example4.shを作成してください。
#!/bin/bash
var1='今は'
var2=`date`
echo ${var1} ${var2}
実行します。(ry
$ ./example4.sh
今は Wed Apr 10 13:18:52 JST 2024
出力
example5.sh を作成してください。
※後で使うので必ず作って!!
#!/bin/bash
var1=`date`
var2=`cat /proc/meminfo | grep "MemFree"`
echo ${var1} ${var2} >> /home/ec2-user/example5.txt
ホームディレクトリは各自の名前に合わせてください。
実行して、./example5.txtの中身を見てください。
どうやってやる?(できるだけ見ないで!)
$ ./example5.sh
$ cat ./example5.txt
Wed Apr 10 13:34:13 JST 2024 MemFree: 1050580 kB
入出力の書き方(一部)
command > file # ファイル作成 or 上書き
command >> file # 追加書き込み ファイルなければ作成
command &>> file # 標準出力・標準エラー出力を同一ファイルに追加書き込み
command >> file 2>&1 # 同上
command < file # ファイル内容をコマンドの標準入力に渡す
今回は
>>
で出力しています。追記されるはずなので、複数回実行して./example5.txtの中身を見てください。
if
if文で分岐させることもできます。
ホームディレクトリに以下の内容を書いたexample6.shを作成してください。
#!/bin/bash
STR1=nifty
STR2=nihuthi
if [ $STR1 != $STR2 ]; then
echo '一致してしません'
else
echo '一致しています'
fi
ifの他の条件文の書き方は検索すれば出てきます。
なお、if文で
[]
を使うのは必須ではなく、今回は
[
コマンドを使っているだけです。難しい話は置いておいて、if文が使えるという事が分かればよいです。それさえ知っていれば検索で使い方は無限に出てきます。興味がある人はif文について検索してみてください。
for
繰り返し処理もできます。
以下の内容を書いたexample7.shを作成してください。
#!/bin/bash
for i in {1..10}; do
echo "Number: $i"
done
実行してください。
配列と組み合わせて使う事が多いです。
以下の内容を書いたexample8.shを作成してください。
#!/bin/bash
# 名前の配列を定義する
names=("John" "Jane" "Bob" "Alice")
# 配列の要素を1つずつ表示する
for name in "${names[@]}"; do
echo "Hello, $name!"
done
実行してください。
発展課題
時間の余った人は挑戦してみてください。答えの例は用意しているので、 AIには聞かず 考えてみてください。
はじめて触る人にとっては難しめです。ヒント見たり検索したりしながら解く前提の難易度にしてます。
(問題と解答例は手抜きでchatGPTに作らせたので、もっといい方法あるかも。ヒントは自分で書きました。)
現在の日時を
YYYY-MM-DD_HH:MM:SS
の形式で表示するシェルスクリプトを書け。
※1行で書けるので、実はシェルスクリプトにする必要もない
ヒント
dateコマンドに引数を設定することで出力形式を指定することができます。やり方は……調べてみてください。
解答例
#!/bin/bash
current_date_time=$(date '+%Y-%m-%d_%H:%M:%S')
echo $current_date_time
システム上で利用可能なシェルの一覧を
/etc/shells
ファイルから取得し、それを表示するシェルスクリプトを書け。
※1行で書けるので、実はシェルスクリプトにする必要もない
ヒント
cat /etc/shells
で使用可能なシェル一覧を見る事ができます。
#からはじまるコメント行があるので、それを除外する必要があります。
どうやって?(追加ヒント)
grepコマンドで絞り込みをします。普通に使うと○○が含まれる行を標準するコマンドですが、オプションを使うと○○を含まない行を出すことが可能です。
ある書き方をすると、○○で始まる行以外を出すことができます。
解答例
#!/bin/bash
cat /etc/shells | grep -v '^#'
現在ログインしているすべてのユーザーのリストを表示するシェルスクリプトを書け。
※1行で書けるので、実はシェルスクリプトにする必要もない
ヒント
who
でログインユーザ一覧を得る事ができます。叩いてみてください。
他にもいろいろ情報があるので、スペース区切りの1つ目だけ取り出せばよいです。
どうやって?(追加ヒント)
色々やり方はありますが、awkコマンドが使えます。 awkコマンド で検索してみてください。
ちなみにawkコマンドはこんな単機能ではなく、いろんなことができます。
解答例
#!/bin/bash
logged_in_users=$(who | awk '{print $1}')
echo "$logged_in_users"
/home/scixxxxx/example5.txtの文字数をカウントし、
example5.txtの文字数は○○文字です
と表示するシェルスクリプトを書け。
ヒント
wc -c
で文字数を得る事ができます。
wc -c < example5.txt
を叩いてみてください。
解答例
#!/bin/bash
# ファイル名を指定
file_name="example5.txt"
# ファイルの文字数をカウントする
char_count=$(wc -c < "$file_name")
# 結果を表示
echo "$file_name の文字数は $char_count 文字です。"
シングルクォート、ダブルクォート、バッククォートの違いについて調べよ。動作の違いが分かるような簡単なシェルスクリプトを書け。
解答例
#!/bin/bash
# 変数の定義
NAME="John Doe"
# シングルクォートの使用
echo 'My name is $NAME' # 変数が展開されない
# ダブルクォートの使用
echo "My name is $NAME" # 変数が展開される
# バッククォートの使用
echo "The current directory is `pwd`" # `pwd`コマンドが実行され、その結果が表示される
カレントディレクトリ内のすべてのファイルとディレクトリの数を別々にカウントして表示するシェルスクリプトを書け。
ヒント
wc -l
で行数を得る事ができます。
wc -l < example5.txt
を叩いてみてください。
解答例
#!/bin/bash
# ディレクトリ内のファイルとディレクトリの数を数える
files=$(ls -1 | wc -l)
dirs=$(find . -maxdepth 1 -type d | wc -l)
echo "ファイルの数: $files"
echo "ディレクトリの数: $dirs"
~/dataディレクトリに事前に5個程度、適当な名前のファイルを用意しておく。(hoge1.txtなど)
~/dataディレクトリにあるファイル名をすべて取得し、
1. hoge1.txt
2. hogehoge2.txt
のように出力するシェルスクリプトを書け
※難しいのでchatgptに聞きながらでも良いです。
ヒント
まずは数字部分を無視して作るとやりやすいです。
配列にディレクトリ内のファイルを取得して格納する
その配列を使ってforループを回す
ループ内で配列内の要素がファイルであることをif文で確かめて、ファイル名をechoで表示する
という流れが自然かなと思います。
(ここまでたどり着く人いるか分からないので答え作ってないです……)
おすすめサイト
https://qiita.com/osw_nuco/items/a5d7173c1e443030875f