シェルの基礎とSSH Tips

  • 61
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

第40回勉強会(2015/02/21) - 長岡 IT開発者 勉強会(NDS)

@hayajo

はじめに

Bourne Shell系の内容です。C Shell系ではないので注意してください。

NOTE:

特に明言しない場合は ash(dash) 準拠となります。


シェルの基礎

コマンド

ブランク区切りの単語のリストです。

find . -type f
ls -la

最初の単語は実行するコマンドとなります。残りの単語はコマンドの引数となります。

NOTE:

変数代入 => ブランク区切りの単語リスト => リダイレクション => 制御演算子

foo=FOO /bin/sh -c 'echo $foo' > foo.txt && echo "done"

コマンドに対して各種展開(チルダ展開、パラメータ展開、コマンド置換、算術式展開、クォート除去など)が行われます。

echo ~
echo ${USER:-unknown}
$(echo "echo foo")
echo $(( 1 + 1 ))
echo "foo bar baz"

変数

値を保持するためのものです。下記のようにして値を代入します。

name=[value]

valueが指定されなかった場合は、値は空文字となります。

$name${name} で展開されます(パラメータ展開)。

name="hello.txt"
echo $name
echo ${name}

NOTE:

ほかにもデフォルト値を指定したりパターンに一致した部分を取り除いたりして展開する形式などもあります。"Parameter Expansion" や 「パラメータ展開」 で調べてみてください。

echo ${name:=hello.world.txt}
hello.txt

echo ${name##*.}
txt

echo ${name%.*}
hello.world

パイプとリダイレクト

パイプ (|) はコマンドの標準出力とコマンドの標準入力を接続します。

ps ux | grep vim | grep -v grep

リダイレクト (>, >>, >&, <, <<, etc.) はコマンドの入出力をファイルに対して行うことができます。

ps ux >out.txt

ps ux >>out.txt

curl -v localhost:5000 >out.txt 2>&1

nc localhost 5000 <in.txt

cat <<EOF >out.txt
foo
bar
buz
EOF

NOTE:

リダイレクトはコマンド実行前に処理されるので、処理するファイルとリダイレクト先のファイルが同じ場合などに注意が必要です。

cat hoge.txt >hoge.txt

リダイレクトについてははこちらに詳しく解説されています。参考にしてください。

制御演算子

コマンド同士を ; で区切ることでコマンドを順番に実行できます。

date; pwd; ls

コマンド同士を && (ANDリスト) で区切ることで前のコマンドの終了ステータスが 0 の場合に後のコマンドが実行されます。

test -e file.txt && cat file.txt

コマンド同士を || (ORリスト) で区切ることで前のコマンドの終了ステータスが 0 以外の場合に後のコマンドが実行されます。

test -e file.txt || touch file.txt

制御構文

if

条件によって処理を分岐させたい場合に使用します。

if [ $(id -u) -eq 0 ]
if> then
if> echo "rootuser"
if> else
if> echo "user"
if> fi

NOTE:

id -u | awk '{if($1==0){ print "rootuser" }else{ print "user" }'

ちなみに [ もコマンドです(testコマンド)。引数が $(id -u) -eq 0 ] となります。

for

与えられた引数の数だけ処理を繰り返し実行したい場合に使用します。

for f in $(ls)
for> do
for> cp $f $f.org
for> done

NOTE:

ls | xargs -I@ cp @ @.org

case

値によって処理を分岐させたい場合に使用します。

case $(read -p 'yN? ' yn; echo $yn) in
case> [yY]* ) echo 'yes!' ;;
case> *) echo 'no...' ;;
case> esac

NOTE:

read -p 'yN? ' yn; echo $yn | awk '{if(tolower($1) ~ /^(y|yes)$/){ print "YES!!" }else{ print "NO..." }}'

while

条件が真である間、処理を繰り返し実行したい場合に使用します。

while read -p '>> ' line && test "$line" != "end"
while> do
while> echo "input: " $line
while> done

コマンドのグループ化

コマンドを括弧で括るとコマンドをグループ化できます。グループ化することで出力をまとめることができます。

(echo hoge; echo fuga; echo piyo) | xargs
(echo hoge; echo fuga; echo piyo) > out.txt

この括弧で括られたコマンドはサブシェルで実行され、シェルの環境に影響を与えるような操作はコマンド終了後に影響を残しません。

hoge=HOGE; (hoge=FUGA; echo $hoge); echo $hoge

cmd="date,pwd,ls"; (IFS=$','; for c in $cmd; do $c; done)

(cd /path/to/app/src; make && make test)

NOTE:

パイプやリダイレクト先もサブシェルで動作するので変数の変更は反映されません。注意してください(「while にパイプで渡す」など)。

シェルスクリプト

1行目にシバン(shebang)を記述し、2行目以降にコマンドを記述します。

#!/bin/sh

set -eu

if [ $# -ne 1 ]; then
  echo "Usage: $0 FILE"
  exit
fi

data=$(cat $1)
res=$(echo -ne "HTTP/1.0 200 OK\nContent-Type: $(echo $file | file --mime-type -b -)\n\n$data")

while true; do
  echo -n "$res" | nc -q 1 -l 8080
done

NOTE:

シバンにはスクリプトを読み込むインタプリタを指定します。シェルスクリプトのファイル本体はインタプリタへのパラメータとなります。

/bin/sh はOSによって異なります(CentOS系ではbash、Debian系ではdash)。dash向けのスクリプトであればほとんどのUNIX系OSで動作しますがbash向けの場合はバージョン違いなどで互換性がない場合があります。同一のスクリプトを複数のOS動作させたい場合は注意してください。

SSH Tips

踏み台

踏み台サーバーを経由してリモートホストに接続する場合は ProxyCommand オプションを利用すると便利です。

  • コマンドラインオプション

    ssh -o ProxyCommand="ssh -W %h:%p USER@BASTION" USER@REMOTE
    
  • ~/.ssh/config

    Host bastion
      User user
    
    Host remote
      User user
      ProxyCommand ssh -W %h:%p bastion
    

NOTE:

いちど踏み台サーバーにログインし、再度sshでリモートホストに接続しなくて良いので嬉しいですね。

ちなみに踏み台サーバーのことは bastion server と呼ぶらしいです。

ProxyCommand を指定するのが面倒なときは

ssh -t USER@BASTION ssh USER@REMOTE

で代替。

リモートホストでパスワード入力など、ターミナルでの操作必要な場合(e.g. sudo とか su とか)はsshの -t(-tt) オプションを指定します。

ssh -t REMOTE_HOST 'su - -c "ls -la /root"'

デモする場合はこんな感じで...

cat <<EOF >Dockerfile
FROM debian

RUN apt-get -y update
RUN apt-get -y install openssh-server && apt-get clean

RUN service ssh start && service ssh stop

RUN useradd -m -s /bin/bash demo
RUN echo root:root | chpasswd
RUN echo demo:demo | chpasswd

CMD ["/usr/sbin/sshd", "-D"]
EOF

docker build -t nds40/sshd .

SSHD_BASTION=$(docker run -d nds40/sshd)
SSHD_DEST=$(docker run -d nds40/sshd)
SSHD_BASTION_IP=$(docker inspect -f '{{.NetworkSettings.IPAddress}}' $SSHD_BASTION)

sudo pipework br1 $SSHD_BASTION 192.168.1.1/24
sudo pipework br1 $SSHD_DEST 192.168.1.11/24

ping 192.168.1.11 # FAIL

ssh -o ProxyCommand="ssh -W %h:%p demo@$SSHD_BASTION_IP" demo@192.168.1.11

# 後片付け
docker rm -f $SSHD_BASTION $SSHD_DEST
sudo ip link dev br1 down
sudo brctl delbr br1

リモートホストのコマンド実行結果を手元で利用

例: リモートホストのログを手元にコピーしつつ検索

ssh REMOTE_HOST 'cat /var/log/httpd/access_log*' | tee REMOTE_HOST.access_log | grep 404

手元のコマンド実行結果をリモートホストで利用

例: 手元のシェルスクリプトをリモートホストで実行

cat batch.sh | ssh REMOTE_HOST 'cat | /bin/sh'

操作ログを残す

scriptコマンドを利用して操作ログをファイルに残します。

script -q -c "ssh REMOTE_HOST" /path/to/log/file

下記のようなスクリプトを用意しておくと良いでしょう。

cat <<EOS >sshl && chmod +x sshl
#!/bin/bash
exec script -q -c "ssh $1" $1.$(date +%Y%m%d_%H%M%S).log
EOS

困ったら

NOTE:

man 1 COMMAND で汎用コマンドとしてマニュアル参照。

環境について

Windowsの環境の方は操作やコマンドに慣れるためにも、仮想化ソフトウェアなどで手元にLinux環境を用意して作業を行うことをおすすめします。

+-----------+
|  Windows  |
|           |
| +-------+ |   SSH     +-------------+
| | Linux +------||-----> REMOTE_HOST |
| +-------+ |           +-------------+
|     ^     |
+-----|-----+
      |
   +--+---+
   | User |
   +------+

個人的なおすすめは Vagrant による環境構築です。Vagrantfile(とプロビジョニング用のファイルなど)を共有することで、別々のマシンでも同様の環境を構築することが可能です。

NOTE:

まさか本番サーバー上で検証作業をしたり運用スクリプトを書いたりテストしたりしていませんよね...

Enjoy!

日常業務をより効率化するにはシェルの基礎のほかに各種コマンドの理解も必要となります。

次のステップではsed/awkを中心に代表的なコマンドの使い方を学びましょう。