シェルの基礎とSSH Tips


[第40回勉強会(2015/02/21) - 長岡 IT開発者 勉強会(NDS)](http://nagaoka.techtalk.jp/no40)

@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

リダイレクトについてはは[こちら](http://shinpeim.github.io/process-book/004.md/)に詳しく解説されています。参考にしてください。

### 制御演算子

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

    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

## 困ったら

* `-h`, `--help` オプションでコマンドのヘルプを読みましょう
* `man COMMAND` でコマンドのマニュアルを読みましょう
* ググれば大抵のことは解決できます
* [「はじめてUNIXで仕事をする人が読む本」(amazon)](http://www.amazon.co.jp/dp/4048913921/)
* [process-book](http://shinpeim.github.io/process-book/)

NOTE:

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

## 環境について

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

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


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

NOTE:

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

## Enjoy!

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

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