今回は、前回までのシェルの機能を使って演習を行う。
演習用のサーバに接続して、シェルスクリプトなどのプログラムを作成する際のプログラムの編集方法にはいくつかの方式がある。
- サーバに接続しているターミナルで編集
- nano , vim , emacs などのエディタで編集
- パソコンで編集してアップロード
- パソコンのエディタのリモートファイルの編集プラグインで編集
- VSCode の remote-ssh プラグインを使うのが簡単だけど、サーバ側の負担が大きいので今回は NG
今回の説明では、emacs で編集する方法を説明する。
guest00@nitfcei.mydns.jp:~$ emacs helloworld.sh
((( Emacs を起動 )))
guest00@nitfcei.mydns.jp:~$ emacs helloworld.sh
((( Emacs を起動 )))
guest00@nitfcei.mydns.jp:~$ emacs helloworld.sh
エディタが起動すると、以下のような画面となる。

scpコマンドは、ssh のプロトコルを使ってネットワークの先のコンピュータとファイルのコピーを行う。前述の emacs などのエディタが使いにくいのなら scp を使えばいい。
((( サーバの helloworld.sh をダウンロード )))
C:\Users\t-saitoh> scp -P 443 guest00@nitfcei.mydns.jp:helloworld.sh .
C:\Users\t-saitoh> scp -P 443 guest00@nitfcei.mydns.jp:/home0/Challenge/3-shellscript/helloworld.sh .
((( パソコンの hoge.sh をアップロード )))
C:\Users\t-saitoh> scp -P 443 hoge.sh guest00@nitfcei.mydns.jp:
((( パソコンの hoge.html を public_html にアップロード )))
C:\Users\t-saitoh> scp -P 443 hoge.html guest00@nitfcei.mydns.jp:public_html
((( scp 命令の使い方 )))
$ scp ユーザ名@ホスト名:ファイルの場所
((( サーバの helloworld.sh をダウンロード )))
C:\Users\t-saitoh> scp -P 443 guest00@nitfcei.mydns.jp:helloworld.sh .
C:\Users\t-saitoh> scp -P 443 guest00@nitfcei.mydns.jp:/home0/Challenge/3-shellscript/helloworld.sh .
((( パソコンの hoge.sh をアップロード )))
C:\Users\t-saitoh> scp -P 443 hoge.sh guest00@nitfcei.mydns.jp:
((( パソコンの hoge.html を public_html にアップロード )))
C:\Users\t-saitoh> scp -P 443 hoge.html guest00@nitfcei.mydns.jp:public_html
((( scp 命令の使い方 )))
$ scp ユーザ名@ホスト名:ファイルの場所
((( サーバの helloworld.sh をダウンロード )))
C:\Users\t-saitoh> scp -P 443 guest00@nitfcei.mydns.jp:helloworld.sh .
C:\Users\t-saitoh> scp -P 443 guest00@nitfcei.mydns.jp:/home0/Challenge/3-shellscript/helloworld.sh .
((( パソコンの hoge.sh をアップロード )))
C:\Users\t-saitoh> scp -P 443 hoge.sh guest00@nitfcei.mydns.jp:
((( パソコンの hoge.html を public_html にアップロード )))
C:\Users\t-saitoh> scp -P 443 hoge.html guest00@nitfcei.mydns.jp:public_html

シェルには、test コマンド( [ コマンド ) で条件判定を行う。動作の例として、テストコマンドの結果を コマンドの成功/失敗 を表す $? を使って例示する。
guest00@nitfcei:~$ [ -f helloworld.sh ] ; echo $? # [ -f ファイル名 ]
guest00@nitfcei:~$ [ -x /bin/bash ]; echo $? # [ -x ファイル名 ]
0 # ファイルが存在して実行可能なら0/だめなら1
guest00@nitfcei:~$ [ -d /opt/local/bin ] ; echo $? # [ -d ディレクトリ名 ]
guest00@nitfcei:~$ [ "$PATH" = "/bin:/usr/bin" ] ; echo $? # [ "$変数" = "文字列" ]
guest00@nitfcei:~$ [ -f helloworld.sh ] ; echo $? # [ -f ファイル名 ]
0 # ファイルがあれば0/なければ1
guest00@nitfcei:~$ [ -x /bin/bash ]; echo $? # [ -x ファイル名 ]
0 # ファイルが存在して実行可能なら0/だめなら1
guest00@nitfcei:~$ [ -d /opt/local/bin ] ; echo $? # [ -d ディレクトリ名 ]
1 # ディレクトリがあれば0/なければ1
guest00@nitfcei:~$ [ "$PATH" = "/bin:/usr/bin" ] ; echo $? # [ "$変数" = "文字列" ]
1 # $変数が"文字列"と同じなら0/違えば1
guest00@nitfcei:~$ [ -f helloworld.sh ] ; echo $? # [ -f ファイル名 ]
0 # ファイルがあれば0/なければ1
guest00@nitfcei:~$ [ -x /bin/bash ]; echo $? # [ -x ファイル名 ]
0 # ファイルが存在して実行可能なら0/だめなら1
guest00@nitfcei:~$ [ -d /opt/local/bin ] ; echo $? # [ -d ディレクトリ名 ]
1 # ディレクトリがあれば0/なければ1
guest00@nitfcei:~$ [ "$PATH" = "/bin:/usr/bin" ] ; echo $? # [ "$変数" = "文字列" ]
1 # $変数が"文字列"と同じなら0/違えば1
if [ -f helloworld.sh ]; then
echo "exist - helloworld.sh"
elif [ -f average.c ]; then
((( シェルの if 文 )))
if [ -f helloworld.sh ]; then
echo "exist - helloworld.sh"
elif [ -f average.c ]; then
echo "exist - average.c"
else
echo "みつからない"
fi
((( シェルの if 文 )))
if [ -f helloworld.sh ]; then
echo "exist - helloworld.sh"
elif [ -f average.c ]; then
echo "exist - average.c"
else
echo "みつからない"
fi
for user in /home0/guests/* # ワイルドカード文字 * があるので、/home0/guests/ のファイル一覧
do # が取り出されて、その1つづつが、$user に代入されながら繰り返し。
結果: /home0/guests/guest00, /home0/guests/guest01 ...
((( シェルの for 文 )))
for user in /home0/guests/* # ワイルドカード文字 * があるので、/home0/guests/ のファイル一覧
do # が取り出されて、その1つづつが、$user に代入されながら繰り返し。
echo $user
done
---
結果: /home0/guests/guest00, /home0/guests/guest01 ...
((( シェルの for 文 )))
for user in /home0/guests/* # ワイルドカード文字 * があるので、/home0/guests/ のファイル一覧
do # が取り出されて、その1つづつが、$user に代入されながら繰り返し。
echo $user
done
---
結果: /home0/guests/guest00, /home0/guests/guest01 ...
/bin/grep ^guest < /etc/passwd \ # passwd ファイルでguestで始まる行を抜き出し、
| while read user # read コマンドで その 行データを $user に代入しながらループ
((( while 文 )))
/bin/grep ^guest < /etc/passwd \ # passwd ファイルでguestで始まる行を抜き出し、
| while read user # read コマンドで その 行データを $user に代入しながらループ
do
echo $user
done
((( while 文 )))
/bin/grep ^guest < /etc/passwd \ # passwd ファイルでguestで始まる行を抜き出し、
| while read user # read コマンドで その 行データを $user に代入しながらループ
do
echo $user
done
guest00@nitfcei:~$ ans=`whoami` # whoami コマンドの結果を ans に代入
guest00@nitfcei:~$ echo $ans # バッククオートに注意 ' シングルクオート " ダブルクオート ` バッククオート
guest00@nitfcei:~$ ans=$(pwd) # pwd コマンドの結果を ans に代入
guest00@nitfcei:~$ echo $ans # 最近は、$(コマンド) の方が良く使われている
((( コマンドの結果を使う )))
guest00@nitfcei:~$ ans=`whoami` # whoami コマンドの結果を ans に代入
guest00@nitfcei:~$ echo $ans # バッククオートに注意 ' シングルクオート " ダブルクオート ` バッククオート
guest00
guest00@nitfcei:~$ ans=$(pwd) # pwd コマンドの結果を ans に代入
guest00@nitfcei:~$ echo $ans # 最近は、$(コマンド) の方が良く使われている
/home0/guest00
((( コマンドの結果を使う )))
guest00@nitfcei:~$ ans=`whoami` # whoami コマンドの結果を ans に代入
guest00@nitfcei:~$ echo $ans # バッククオートに注意 ' シングルクオート " ダブルクオート ` バッククオート
guest00
guest00@nitfcei:~$ ans=$(pwd) # pwd コマンドの結果を ans に代入
guest00@nitfcei:~$ echo $ans # 最近は、$(コマンド) の方が良く使われている
/home0/guest00
シェルの中でコマンドライン引数を参照する場合には、”$数字“, “$@” を使う。$1 , $2 で最初のコマンドライン引数, 2番目のコマンドライン引数を参照できる。すべてのコマンドライン引数を参照する場合には、$@ を使う。
((( argv.sh : コマンドライン引数を表示 )))
guest00@nitfcei:~$ chmod 755 argv.sh
guest00@nitfcei:~$ ./argv.sh abc 111 def
abc 111 def # echo "$@" の結果
((( argv.sh : コマンドライン引数を表示 )))
#!/bin/bash
echo "$@"
for argv in "$@"
do
echo "$argv"
done
((( argv.sh を実行 )))
guest00@nitfcei:~$ chmod 755 argv.sh
guest00@nitfcei:~$ ./argv.sh abc 111 def
abc 111 def # echo "$@" の結果
abc # for argv ... の結果
111
def
((( argv.sh : コマンドライン引数を表示 )))
#!/bin/bash
echo "$@"
for argv in "$@"
do
echo "$argv"
done
((( argv.sh を実行 )))
guest00@nitfcei:~$ chmod 755 argv.sh
guest00@nitfcei:~$ ./argv.sh abc 111 def
abc 111 def # echo "$@" の結果
abc # for argv ... の結果
111
def
guest00@nitfcei:~$ cut -d: -f 1 /etc/passwd # -d: フィールドの区切り文字を : で切り抜き
guest00@nitfcei:~$ awk -F: '{print $1}' /etc/passwd # -F: フィールド区切り文字を : で切り分け
((( 行の特定部分を抜き出す )))
guest00@nitfcei:~$ cut -d: -f 1 /etc/passwd # -d: フィールドの区切り文字を : で切り抜き
root # -f 1 第1フィールドだけを出力
daemon
adm
:
guest00@nitfcei:~$ awk -F: '{print $1}' /etc/passwd # -F: フィールド区切り文字を : で切り分け
root # ''
daemon
adm
:
((( 行の特定部分を抜き出す )))
guest00@nitfcei:~$ cut -d: -f 1 /etc/passwd # -d: フィールドの区切り文字を : で切り抜き
root # -f 1 第1フィールドだけを出力
daemon
adm
:
guest00@nitfcei:~$ awk -F: '{print $1}' /etc/passwd # -F: フィールド区切り文字を : で切り分け
root # ''
daemon
adm
:
t-saitoh pts/1 64.33.3.150 Thu Jul 7 12:32 still logged in
guest00@nitfcei:~$ last | grep guest
guest15 pts/11 192.156.145.1 Tue Jul 5 16:00 - 16:21 (00:21)
((( 7/5にログインしたguestで、名前だけを取り出し、並び替えて、重複削除 )))
guest00@nitfcei:~$ last | grep guest | grep "Jul 5" | awk '{print $1}' | sort | uniq
7/5("Jul 5")の授業で演習に参加していた学生さんの一覧が取り出せる。
### あれ、かなりの抜けがあるな!?!? ###
((( ログイン履歴を確認 )))
guest00@nitfcei:~$ last
t-saitoh pts/1 64.33.3.150 Thu Jul 7 12:32 still logged in
最近のログインした名前とIPアドレスの一覧
:
((( guest* がログインした履歴 )))
guest00@nitfcei:~$ last | grep guest
guest15 pts/11 192.156.145.1 Tue Jul 5 16:00 - 16:21 (00:21)
:
((( 7/5にログインしたguestで、名前だけを取り出し、並び替えて、重複削除 )))
guest00@nitfcei:~$ last | grep guest | grep "Jul 5" | awk '{print $1}' | sort | uniq
7/5("Jul 5")の授業で演習に参加していた学生さんの一覧が取り出せる。
### あれ、かなりの抜けがあるな!?!? ###
((( ログイン履歴を確認 )))
guest00@nitfcei:~$ last
t-saitoh pts/1 64.33.3.150 Thu Jul 7 12:32 still logged in
最近のログインした名前とIPアドレスの一覧
:
((( guest* がログインした履歴 )))
guest00@nitfcei:~$ last | grep guest
guest15 pts/11 192.156.145.1 Tue Jul 5 16:00 - 16:21 (00:21)
:
((( 7/5にログインしたguestで、名前だけを取り出し、並び替えて、重複削除 )))
guest00@nitfcei:~$ last | grep guest | grep "Jul 5" | awk '{print $1}' | sort | uniq
7/5("Jul 5")の授業で演習に参加していた学生さんの一覧が取り出せる。
### あれ、かなりの抜けがあるな!?!? ###
guest00@nitfcei:~$ whois 192.156.145.1
inetnum: 192.156.145.0 - 192.156.148.255
guest00@nitfcei:~$ whois 192.156.145.1 | grep netname:
netname: ANCT-CIDR-BLK-JP
((( IPアドレスなどの情報を調べる )))
guest00@nitfcei:~$ whois 192.156.145.1
:
inetnum: 192.156.145.0 - 192.156.148.255
netname: FUKUI-NCT
country: JP
:
guest00@nitfcei:~$ whois 192.156.145.1 | grep netname:
netname: FUKUI-NCT
netname: ANCT-CIDR-BLK-JP
((( IPアドレスなどの情報を調べる )))
guest00@nitfcei:~$ whois 192.156.145.1
:
inetnum: 192.156.145.0 - 192.156.148.255
netname: FUKUI-NCT
country: JP
:
guest00@nitfcei:~$ whois 192.156.145.1 | grep netname:
netname: FUKUI-NCT
netname: ANCT-CIDR-BLK-JP
ここまでのプログラムの動作例では、a.out などのプログラムを実行する際には、先頭に “./” をつけて起動(./a.out)している。これは「このフォルダ(“./“)にある a.out を実行せよ」との意味となる。
いちいち、カレントフォルダ(“./”)を先頭に付けるのが面倒であっても、環境変数 PATH を “export PATH=.:/bin:/usr/bin” などと設定してはいけない。こういった PATH にすれば、”a.out” と打つだけでプログラムを実行できる。しかし、”ls” といったファイル名のプログラムを保存しておき、そのフォルダの内容を確認しようとした他の人が “ls” と打つと、そのフォルダの中身を実行してしまう。
guest00@nitfcei:~$ export PATH=".:/bin:/bin/bash"
guest00@nitfcei:~$ cat /home0/Challenge/1-CTF.d/Task5/Bomb/ls
guest00@nitfcei:~$ cd /home0/Challenge/1-CTF.d/Task5/Bomb
# 接続が切れる(bashが強制停止となったため)
guest00@nitfcei:~$ export PATH=".:/bin:/bin/bash"
guest00@nitfcei:~$ cat /home0/Challenge/1-CTF.d/Task5/Bomb/ls
#!/bin/bash
killall -KILL bash
guest00@nitfcei:~$ cd /home0/Challenge/1-CTF.d/Task5/Bomb
guest00@nitfcei:~$ ls
# 接続が切れる(bashが強制停止となったため)
guest00@nitfcei:~$ export PATH=".:/bin:/bin/bash"
guest00@nitfcei:~$ cat /home0/Challenge/1-CTF.d/Task5/Bomb/ls
#!/bin/bash
killall -KILL bash
guest00@nitfcei:~$ cd /home0/Challenge/1-CTF.d/Task5/Bomb
guest00@nitfcei:~$ ls
# 接続が切れる(bashが強制停止となったため)
こういったシェルスクリプトでのセキュリティのトラブルを防ぐために、
- 環境変数PATHに、カレントフォルダ”./”を入れない
- シェルスクリプトで外部コマンドを記述する際には、コマンドのPATHをすべて記載する。
コマンドのPATHは、which コマンドで確認できる。echo とか [ といったコマンドは、bash の組み込み機能なので、コマンドのPATHは書かなくていい。
シェルスクリプトの練習として、以下の条件を満たすものを作成し、スクリプトの内容の説明, 機能, 実行結果, 考察を記載したワードファイル(or PDF)等で、こちらのフォルダに提出してください。
- スクリプトとして起動して結果が表示されること。(シバン,実行権限)
- コマンドライン引数を使っていること。
- 入出力リダイレクトやパイプなどを使っていること。
- 以下の例を参考に。
((( 第1コマンドライン引数指定したユーザが、福井高専からアクセスした履歴を出力する。)))
if [ -x /usr/bin/last -a -x /bin/grep ]; then # [ ... -a ... ] は、複数条件のAND
/usr/bin/last "$1" | /bin/grep 192.156.14
-------------------------------------------------------------------------
((( guest グループで、$HOME/helloworld.sh のファイルの有無をチェック )))
for dir in /home0/guests/*
if [ -f "$dir/helloworld.sh" ]; then # PATHの最後の部分を取り出す
echo "$(/usr/bin/basename $dir)" # $ basename /home0/guests/guest00
fi # guest00 ~~~~~~~basename
((( 第1コマンドライン引数指定したユーザが、福井高専からアクセスした履歴を出力する。)))
#!/bin/bash
if [ -x /usr/bin/last -a -x /bin/grep ]; then # [ ... -a ... ] は、複数条件のAND
/usr/bin/last "$1" | /bin/grep 192.156.14
fi
-------------------------------------------------------------------------
((( guest グループで、$HOME/helloworld.sh のファイルの有無をチェック )))
#!/bin/bash
for dir in /home0/guests/*
do
if [ -f "$dir/helloworld.sh" ]; then # PATHの最後の部分を取り出す
echo "$(/usr/bin/basename $dir)" # $ basename /home0/guests/guest00
fi # guest00 ~~~~~~~basename
done
((( 第1コマンドライン引数指定したユーザが、福井高専からアクセスした履歴を出力する。)))
#!/bin/bash
if [ -x /usr/bin/last -a -x /bin/grep ]; then # [ ... -a ... ] は、複数条件のAND
/usr/bin/last "$1" | /bin/grep 192.156.14
fi
-------------------------------------------------------------------------
((( guest グループで、$HOME/helloworld.sh のファイルの有無をチェック )))
#!/bin/bash
for dir in /home0/guests/*
do
if [ -f "$dir/helloworld.sh" ]; then # PATHの最後の部分を取り出す
echo "$(/usr/bin/basename $dir)" # $ basename /home0/guests/guest00
fi # guest00 ~~~~~~~basename
done