Paepoi

Paepoi » ShellScript Tips » ShellScript Tips | シェルスクリプトの基本

ShellScript Tips | シェルスクリプトの基本

# 最終更新日 2019.12.01

- macOS zsh 関連の追記、及びそれに合わせた書き換え。
- プロンプトの $ 記号を削除(zsh は % なので)、出力は #=> 表記に変更。

GUI ばかり使う人でも「これくらいは知っておこう」的な解説

コマンド
UNIX 系のコマンドとは極端に言うと Windows でいう CUI アプリケーション。
基本 コマンドを Linux コマンドという人もいるけど正確には POSIX コマンドである。

macOS や Windows とは違い Linux では CUI と GUI に違いは無い。
なので Linux では gedit 等の GUI もコマンドの一つです。
which, type というコマンドを探すコマンドがあるので試してみましょう。
# Fedora bash4 の場合
# /bin は /usr/bin へのシンボリックリンクになっている

which cp
#=> /usr/bin/cp

which echo
#=> /usr/bin/echo

which gedit
#=> /usr/bin/gedit

type [
#=> [ はシェル組み込み関数です

type [[
#=> [[ はシェルの予約語です

type google-chrome
#=> google-chrome は /usr/bin/google-chrome です

よく利用する cp, mv. rm なんかも、更には echo さえも実行ファイルが見つかる。
if, case 等の制御文も type コマンドのとおりです。
ただ基本コマンドの echo 等の実行ファイルは組み込みコマンドを呼び出ししているだけ。
解説すると長いので省略、興味があるなら下記リンク先などで。
同名コマンドのファイルがなぜ多数存在するのか?

そのまま利用できるコマンドはこのような組み込みコマンド。
又は環境変数 PATH で指定されているディレクトリにあるコマンドです。
echo $PATH
尚、コマンド自身がスクリプトであることも多いです、/usr/bin を覗いてみれば解ります。
コマンドを追加したい場合 Linux は /usr/bin に入れるのが基本になっています。
macOS はサンドボックスがあるのでインストーラに従ってください。

単純なアプリケーションであればそのまま入っているだけですが複雑な形式になっている場合もあります。
たとえば Linux 版 Google Chrome は /opt/google/chrome に入り実行スクリプトも同じ位置にある。
/usr/bin にはそのシェルスクリプトへのシンボリックリンクがあるという仕組みになっています。

/bin/sh
/bin/sh は Redhat 系ディストロでは bash へのシンボリックリンクです。
macOS では少し複雑ですが結果的には bash を起動します。
Debian 系はご存知 dash という超しょぼいシェルを呼び出します。
FreeBSD 等もしょぼ...とにかく sh=bash ではなくベンダーによって様々です。

bash を sh という名前で起動すると POSIX sh 互換で動作するようになります。
/bin/sh と /bin/bash の違い - 双六工場日誌

/bin/sh で呼び出しする場合は bash 依存コマンドを書かないように。
とかよく見かけますが個人の勝手でいい、思想の押しつけなんて糞食らえです。
macOS のデフォルトシェルが zsh になったので少しだけ注意したほうがいいかも。

パスを通す、実行する
環境変数 PATH には自分の都合がよいディレクトリを追加することもできます。
たとえば自分のホーム以下に app というディレクトリを作りソレを追加するには
export PATH=$PATH:$HOME/app
という具合に指定、但し追加したインスタンス内でしか有効になりません。

常に有効にしたい場合は bash(~/.bashrc), zsh(~/.zshrc) に上記を追記する。
このファイルはログイン時に読み込まれるファイルです。

Fedora では ~/.bashrc に以下の指定が最初からあります。
ので ~/bin を作れば再ログインで勝手に通ります。
PATH=$PATH:$HOME/.local/bin:$HOME/bin

又、環境変数 PATH に入っていなくても絶対パスを指定すれば実行することができます。
カレントディレクトリのスクリプトなら ./ をコマンドの先頭に付加すれば実行できます。
更に端末から sh コマンドに引き渡せば実行パーミッションを付ける必要もありません。
sh testfile.sh #例

カレントディレクトリ
カレントディレクトリとはユーザが現在作業を行なっているディレクトリです。

gnome-terminal を普通に起動すると自身のホームがカレントディレクトリになります。
Terminal.app でも同様です。

Nautilus でファイル W クリックは開いているディレクトリがカレントディレクトリに設定される。
Finder ではルート (/) になるので注意。

コマンドはカレントディレクトリから cd コマンドで移動。
その場所にあるファイルに対してコマンドを実行する、という流れです。
macOS, Windows で GUI アプリは何をやるのも絶対パスですがコマンドではそれが普通です。

たとえばあるディレクトリの ZIP ファイルのみを全削除したい場合は移動後に
rm *.zip
するとそのディレクトリ内の zip ファイルのみが全削除される、という動作をします。
他のディレクトリにある zip ファイルには影響ありません。

端末のおさらい
改行したくない「 ; 」
大好きな日本人が多いワンライナー、筆者は読み辛いので嫌い。
; の前後コマンドに何の関連性もありません、どうしても改行したくない場合のみ。
echo abcd ; echo えーびーしぃでぇ

子シェルで実行「 () 」
コマンドを () でくくると子シェル、つまり別のシェルで実行させた扱いになります。
非同期ではありませんので混同しないように。
() を脱出した時の環境に影響を与えません、上記 ; と組み合わせると良いでしょう。
(cd /usr/bin ; pwd)
#=> /usr/bin
pwd
#=> /home/sasakima-nao

パイプ「 | 」
前方のコマンドを実行して得た結果を後方のコマンドに渡す場合はパイプを利用する。
以下の例は /usr/bin 内にある python コマンドの一覧を表示。
ls /usr/bin | grep python

バックグラウンドで実行「 & 」
端末からコマンドを起動するとソレを終了するまで処理が戻ってこない。
バックグラウンドで実行させるにはコマンドの後ろに & を付ける。
# Linux では GUI もコマンドなので解りやすい
gnome-calculator &

# & の後ろにコマンドを書けば同時起動もできる、使い道はともかく...
gnome-calculator & gedit

# ついでに、GNOME では Nautilus では & はいらない
# ファイルマネージャは常に起動しているので GApplication の仕組みで転送により終了する
# Gedit を起動した状態で端末から Gedit を起動等も同じようになる
nautilus

終了コードを調べる「 $?, &&, || 」
C 言語で main 関数の最後に return 0; と書く、途中で何事かがあればゼロ以外を return させる。
のは C 言語を少しでも勉強した皆さんならばご存知のはず。
これは何事も起こらず最後まで到達したらゼロ(正常終了)という終了コードを戻しているのです。
cat 存在するファイル.txt
echo $?
#=> 0

cat 存在しないファイル.txt
echo $?
#=> 1
&& はゼロなら次のコマンドを実行、|| は逆の動作を行います。
echo 新規書き込み > a.txt && echo 書き込み成功
#=> 書き込み成功

cd /
echo 新規書き込み > a.txt && echo 書き込み成功 || echo 書き込み失敗
#=> 書き込み失敗

コマンドの結果を引数に入れる「 `` 」
# バッククォートでコマンドを囲むと結果を引数に指定できます
echo 現在 `pwd` にいます

# 変数に入れることもできる
text=`cat .bashrc`

# $() でもいい
echo ホスト名は$(uname -n)です

コマンドを途中で改行「 \ 」
# 区切りを見やすくする、やる人はいないだろうけどコマンド名の途中でも改行可能
ec\
> ho あいうえお
#=> あいうえお

ホーム「 ~ 」
cd /usr/bin
cd ~   # ホームに戻る
echo ~ # ホーム位置が表示される

特殊ディレクトリ「 . 」「 .. 」
# . が現在のパスに展開される
./test.py

# 一つ上の階層ディレクトリに異動
cd ..

# GNOME で現在のカレントディレクトリでファイルマネージャーを起動
gio open .

# macOS の場合
open .

リダイレクト「 >, >>, <, << 」
標準出力先を変更する、端末に出力される結果をテキストファイル等で書き出す。
> なら書き換え
>> なら追記となる。
echo `pwd`のファイル一覧 > d.txt
ls -l >> d.txt
標準出力なので gnome-terminal を2つ起動してでこんなこともできます。
tty
#=> /dev/pts/0
別の端末から
echo あっちに出力 > /dev/pts/0 # あっちに出力される
< はあまり使い道が無い、cat の代わりなんかに使える。
cat bike.txt | grep SUZUKI
# 上記と動作は同じ
grep SUZUKI < bike.txt

<< はヒアドキュメントで主に使う。
端末上では使い道が少ないけど改行付きの長い文字列を簡単に扱える。
cat << _EOS_ > filelist.txt
> `pwd` のファイル一覧
> 作成日
> `date`
>
> `ls -1`
> _EOS_

標準エラー出力のリダイレクトは 2>, 2>> と打ち込む。
cat 存在しないファイル.txt > c.log 2>> error.log

メタ文字
Linux ではファイル名に半角スラッシュ以外は何でも利用できます。
macOS では同様にコロン以外は何でも利用できます。

しかし困ったことにシェルでは利用できないメタ文字が沢山あります。
記号は全滅、タブ文字や半角スペースは区切りという意味になる。
cat My Documents.txt
上記では[My] と [Documents.txt] という2つのファイルを読み込もうとする。
この場合はクォートで囲むかバックスラッシュ記号(エスケープ文字)を付ける。
cat "My Documents.txt"
cat 'My Documents.txt'
cat My\ Documents.txt
# 記号を使ったファイルも同様に
cat \#012.txt
cat "@1st.txt"
シングルクォート内はシングルクォート以外のエスケイプは不要。
ダブルクォート内も同様だけど $, ` のメタ文字は本来の意味を保持する。
つまり変数やコマンド結果を含めるかどうかを選択できる。
SUZUKI=かっこいい
echo '$SUZUKI `uname`'
#=> $SUZUKI `uname`
echo "$SUZUKI `uname`"
#=> かっこいい Darwin

エスケープシーケンスというのもあります。

エスケープシーケンス 意味
\aベルを鳴らす
\bBackSpace
\fフォーム フィード
\n改行
\rキャリッジ リターン
\t水平タブ
\v垂直タブ
\\\ を表示
\?リテラル疑問符
\'シングルクォート
\"ダブルクォート
\0NULL 文字
\数値8 進数
\x数値16 進数

C 言語の経験があるならお馴染ですよね。
# bash4
echo -e "これで\n改行できる"
# bash3
printf "bash3 の echo は\n-e オプションが無い\n"
# zsh
echo "zsh は\nオプション不要"
たとえばソースコードで半角スペース4つのインデントを水平タブに全置換したものを作る場合
cat hankaku.c | sed 's/    /\t/g' > tab.c

変数
変数の名前はアルファベット(大文字小文字は区別する)、数字、アンダースコア(_)を利用できる。
但し名前の先頭に数値は指定できない。
値を代入するには = を使う、イコールの前後に空白(スペースやタブ)を入れてはいけない。

変数を参照する場合は $ を名前の前に付ける。
初期化が終わっていない(何も代入していない、未宣言)変数を参照すると NULL として扱われます。
以下のような場合には参照できません(あまりこういう場合は無いですが)
name=Suzuki
echo $nameKatana #=> NULL
変数名がどこからどこまでか判断できないということです、こういう場合は
name=Suzuki
echo ${name}Katana #=> SuzukiKatana
と { } で変数名を囲む、囲むのが正しい表現とはこういうことです。

現在定義されている変数を確認するには以下のコマンドを使います。
set      # 現在 Shell で定義されている変数一覧
env      # 現在 Shell で定義されている環境変数一覧
readonly # 現在 Shell で定義されている読み込み専用変数一覧
定義した環境変数はそのシェルから起動したアプリに引き継ぎできる。
export katana=かっこいい
tcsh # このシェルから $katana が参照できる
変数に入るもの、というか扱えるものはすべて文字列です。
NUM=3
echo $NUM + 6 #=> 3 + 6
Perl や Python と違い整数として扱ったり計算をするのに一手間掛かります。
別ページにて解説。

スクリプトファイル
UNIX 系ではテキストファイルの一行目は特別な意味があります。
Fedora 等の Linux では
#!/usr/bin/firefox

このファイルを火狐で開きます
macOS では以下がいいかな
#!/usr/bin/osascript

display alert "ハローワールド"
のスクリプトを test という拡張子の無いファイルで保存したとします。
chmod +x test で実行パーミッションを付け ./test で実行するとあら不思議。

つまり実行パーミッションがある場合はその一行目アプリにファイルが引き渡されます。
#! の後にフルパスで指定する、又は env を使って $PATH の中から探せるようにする。
シバンといいます。

Nautilus 等が拡張子が無いファイルを見分けているように UNIX 系では拡張子は無意味です。
(Nautilus は /usr/share/mime/magic 等を使って見分けている)
又 CUI と GUI にまったく区別が無いということも上記で解ると思います。
macOS でもコマンド関連は他の UNIX 系と同様です。

ということでスクリプトファイルは一行目にシバンが必要です。
Copyright(C) sasakima-nao All rights reserved 2002 --- 2020.