Mac ターミナルの基本的な使い方・操作方法(3)シェルスクリプト

シェルの環境設定やパーミッション、シェルスクリプト(変数、引数、配列、構文、条件判定、四則演算など)などの基本的なことについての覚書です。

作成日:2019年10月08日

関連ページ

シェル

ユーザがターミナルを開くとシェルというプログラムが起動してプロンプトを表示し、コマンド入力待ちの状態になります。

プロンプトの後にユーザがコマンドを入力するとシェルによって解釈されコンピュータ(OS の中心部分:カーネル)に伝えられます。

そしてコンピュータがコマンドを実行し、応答をシェルが受け取り再びシェルがプロンプトを表示します。

言い換えると、ユーザは直接コンピュータとやり取りするのではなく、ユーザはシェルを通してコンピュータ(カーネル)にコマンドを送り、シェルを通して結果を受け取るというようにシェルを介してコンピュータを操作します。

関連項目:シェルの展開順序

シェルには大きく分けて、上記のコマンドインタープリタ(OS のインターフェース)としての役割とプログラム言語としての役割があります。

macOS の標準シェル(bash)

シェルにはいろいろな種類のシェルがあり、macOS にも複数のシェルが用意されていますが、デフォルトで使われるのは bash(Bourne Again SHell)です。

システムにログインした際に使用されるシェルを「ログインシェル」と呼びます。

現在使用されているシェルは以下のように環境変数 $SHELL で確認することができます。

echo $SHELL  return
/bin/bash

man bash

man コマンドの引数に bash を指定して実行すると bash の非常に詳しい情報(シェルの文法のようなもの:変数、特殊文字、制御構造、構文、各種展開、等々)が表示されます。

man bash    return
BASH(1)                                                                BASH(1)

NAME
       bash - GNU Bourne-Again SHell

SYNOPSIS
       bash [options] [file]

COPYRIGHT
       Bash is Copyright (C) 1989-2005 by the Free Software Foundation, Inc.

DESCRIPTION
       Bash  is  an  sh-compatible  command language interpreter that executes
       commands read from the standard input or from a file.  Bash also incor-
       porates useful features from the Korn and C shells (ksh and csh).

       Bash  is  intended  to  be a conformant implementation of the Shell and
       Utilities portion  of  the  IEEE  POSIX  specification  (IEEE  Standard
       1003.1).  Bash can be configured to be POSIX-conformant by default.

・・・

シェルの変更

ターミナルで使用するログインシェルを変更するには、ターミナルの「環境設定」で「一般」を選択します。

「開くシェル」で「デフォルトのログインシェル」が選択されいる場合は、bash がログインシェルとして使われます。

ターミナルの「環境設定」で「一般」のスクリーンショット

ログインシェルを変更するには、「開くシェル」で「コマンド」を選択して使用するシェルのパスを入力します。

以下は、ログインシェルに zsh を指定する例です。

ターミナルの「環境設定」で「一般」でシェルを zsh に変更した場合ののスクリーンショット

ターミナルにログインした後に別のシェルを使用するにはシェルをコマンドとして実行すれば、ログインシェル(bash)の上でそのシェルが起動します。

exit コマンドを実行すれば元のシェルに戻ります。(以下の例ではプロンプトも表示しています)

$ zsh  return #zsh に変更

% #プロンプトが % に変わる

% echo $PS1  return #$PS1 を表示
%m%# 

% exit  return #zsh を終了

$  # bash に戻り、プロンプトが $ に戻る

$ ls /bin | grep 'sh'  return # /bin ディレクトリ内の sh を含む名前を表示
bash
csh
ksh
sh
tcsh
zsh

以下のようなシェルがあります(一部抜粋)。

主なシェルの例
シェル 説明
sh 最も古くからあるシェル。開発者の名前から Bourne シェルや「B シェル」と呼ばれる。
bash sh の上位互換シェル。「生まれ変わった Bourne シェル」という意味で bash(Bourne Again SHell)と名付けられる。macOS 標準シェル。
ksh Korn Shell。David Korn が開発。sh の上位互換シェル。
csh C シェル/C Shell。C 言語の文法を元にしているのが名前の由来。sh とは互換性なし。
tcsh csh の機能拡張版。TC シェル(TENEX C shell)
zsh 最も高機能なシェルの1つ。sh、bash、csh、ksh、tcsh の上位互換(相当の機能を持つ)。

内部コマンドと外部コマンド

コマンドにはシェルの中に組み込まれている内部コマンド(組み込みコマンド、ビルトインコマンド、シェルコマンドとも呼びます)と、実行可能形式のファイル(executable file)として保存されている外部コマンドがあります。

コマンドラインでコマンドを実行する際、シェルは以下の順番で該当する名前のコマンドを探し、最初に見つけたものを実行します。

  1. エイリアス
  2. 内部コマンド(組み込みコマンド)
  3. 外部コマンド(実行可能なファイル):環境変数 PATH にセットされているコマンド検索パス(コマンドサーチパス)を先頭から順に検索

また、内部コマンド(組み込みコマンド)はシェル自体に実装されいるコマンドなので、シェルの振る舞いを変更したり、制御構文としてのコマンドなどが用意されています。

例えば、printenv は外部コマンドなのでシェル変数にはアクセスできませんが、echo コマンドはシェルの内部コマンドなのでシェル変数及び環境変数にアクセスできます。

bash の内部コマンドは引数なしで help と入力することで表示させることができます。

help   return
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)
These shell commands are defined internally.  Type `help' to see this list.
Type `help name' to find out more about the function `name'.
Use `info bash' to find out more about the shell in general.
Use `man -k' or `info' to find out more about commands not in this list.

A star (*) next to a name means that the command is disabled.

 JOB_SPEC [&]                       (( expression ))
 . filename [arguments]             :
 [ arg... ]                         [[ expression ]]
 #内部コマンドのリスト(適当に改行しています)
 alias [-p] [name[=value] ... ]     
 bg [job_spec ...]
 bind [-lpvsPVS] [-m keymap] [-f fi 
 break [n]
 builtin [shell-builtin [arg ...]]  
 caller [EXPR]
 case WORD in [PATTERN [| PATTERN]. 
 cd [-L|-P] [dir]
 command [-pVv] command [arg ...]   
 compgen [-abcdefgjksuv] [-o option
 complete [-abcdefgjksuv] [-pr] [-o continue [n]
 declare [-afFirtx] [-p] [name[=val 
 dirs [-clpv] [+N] [-N]
 disown [-h] [-ar] [jobspec ...]    
 echo [-neE] [arg ...]
 enable [-pnds] [-a] [-f filename]  
 eval [arg ...]
 exec [-cl] [-a name] 
 file [redirec exit [n]
 export [-nf] [name[=value] ...] or false
 fc [-e ename] [-nlr] [first] [last 
 fg [job_spec]
 for NAME [in WORDS ... ;] do COMMA for (( exp1; exp2; exp3 )); do COM
 function NAME { COMMANDS ; } or NA 
 getopts optstring name [arg]
 hash [-lr] [-p pathname] [-dt] [na 
 help [-s] [pattern ...]
 history [-c] [-d offset] [n] or hi if COMMANDS; then COMMANDS; [ elif
 jobs [-lnprs] [jobspec ...] or job kill [-s sigspec | -n signum | -si
 let arg [arg ...]                  
 local name[=value] ...
 logout                             
 popd [+N | -N] [-n]
 printf [-v var] format [arguments] 
 pushd [dir | +N | -N] [-n]
 pwd [-LP]                          
 read [-ers] [-u fd] [-t timeout] [
 readonly [-af] [name[=value] ...]  
 return [n]
 select NAME [in WORDS ... ;] do CO 
 set [--abefhkmnptuvxBCHP] [-o opti
 shift [n]                          
 shopt [-pqsu] [-o long-option] opt
 source filename [arguments]        
 suspend [-f]
 test [expr]                        
 time [-p] PIPELINE
 times                              
 trap [-lp] [arg signal_spec ...]
 true                               
 type [-afptP] name [name ...]
 typeset [-afFirtx] [-p] name[=valu 
 ulimit [-SHacdfilmnpqstuvx] [limit
 umask [-p] [-S] [mode]             
 unalias [-a] name [name ...]
 unset [-f] [-v] [name ...]         
 until COMMANDS; do COMMANDS; done
 variables - Some variable names an 
 wait [n]
 while COMMANDS; do COMMANDS; done  { COMMANDS ; }
          

内部コマンドの場合、man コマンドで使い方を調べようとしても見つからなかったり、内部コマンド全体の説明のようなものが表示される場合があります。

man let   return # let コマンドのマニュアルは見つからない
No manual entry for let  

man read    return # read コマンドの場合、以下のように内部コマンドの説明が表示される 
 
BUILTIN(1)                BSD General Commands Manual               BUILTIN(1)

NAME
     builtin, !, %, ., :, @, {, }, alias, alloc, bg, bind, bindkey, break,
     breaksw, builtins, case, cd, chdir, command, complete, continue, default,
     dirs, do, done, echo, echotc, elif, else, end, endif, endsw, esac, eval,
     exec, exit, export, false, fc, fg, filetest, fi, for, foreach, getopts,
     glob, goto, hash, hashstat, history, hup, if, jobid, jobs, kill, limit,
     local, log, login, logout, ls-F, nice, nohup, notify, onintr, popd,
     printenv, pushd, pwd, read, readonly, rehash, repeat, return, sched, set,
     setenv, settc, setty, setvar, shift, source, stop, suspend, switch,
     telltc, test, then, time, times, trap, true, type, ulimit, umask,
     unalias, uncomplete, unhash, unlimit, unset, unsetenv, until, wait,
     where, which, while -- shell built-in commands

SYNOPSIS
     builtin [-options] [args ...]
・・・

help の引数に内部コマンド名を指定すると、そのコマンドの使い方が表示されます。

help let  return #let コマンドの使い方を表示
          
let: let arg [arg ...]
    Each ARG is an arithmetic expression to be evaluated.  Evaluation
    is done in fixed-width integers with no check for overflow, though
    division by 0 is trapped and flagged as an error.  The following
    list of operators is grouped into levels of equal-precedence operators.
    The levels are listed in order of decreasing precedence.
    
    	id++, id--	variable post-increment, post-decrement
    	++id, --id	variable pre-increment, pre-decrement
    	-, +		unary minus, plus
    	!, ~		logical and bitwise negation

      ・・・
      
help read  return #read コマンドの使い方を表示

read: read [-ers] [-u fd] [-t timeout] [-p prompt] [-a array] [-n nchars] [-d delim] [name ...]
    One line is read from the standard input, or from file descriptor FD if the
    -u option is supplied, and the first word is assigned to the first NAME,
    the second word to the second NAME, and so on, with leftover words assigned
    to the last NAME.  Only the characters found in $IFS are recognized as word
    delimiters.  If no NAMEs are supplied, the line read is stored in the REPLY
    variable.  If the -r option is given, this signifies 'raw' input, and
    backslash escaping is disabled.  The -d option causes read to continue
    until the first character of DELIM is read, rather than newline.  If the -p
    option is supplied, the string PROMPT is output without a trailing newline
    before attempting to read.  If -a is supplied, the words read are assigned
    
     ・・・
type コマンド

コマンドが内部コマンドと外部コマンドのどちらのタイプかは type コマンドで調べることができます。

以下は type コマンドの書式です。

type [オプション] コマンド名

type コマンドを実行すると、シェルがどのコマンドを起動しているかを確認することができます。

内部コマンドの場合は「xxxx is a shell builtin」と表示され、外部コマンドの場合はファイルの保存先のパスが表示されます。

type echo   return 
echo is a shell builtin  #内部コマンド

type find   return 
find is /usr/bin/find  #外部コマンド

type ls   return 
ls is hashed (/bin/ls)  #外部コマンド(ハッシュテーブルに記憶されている)

「xxxx is hashed」と表示されるのは、シェル(bash)がこのコマンドの位置を既にハッシュテーブル(コマンドの位置を記憶しておくテーブル)に記憶していて、環境変数 PATH のディレクトリを検索せずにそのコマンドを実行するという意味です。

実行可能なコマンドを全て表示する -a

コマンドによっては内部コマンドと外部コマンドの両方が用意されていたり、エイリアスが設定されている場合があります。

-a オプションを指定することで全てを表示することができます。

以下は ls コマンドにエイリアスが設定されている場合の例です。

alias ls='ls -l'   return   #エイリアスを設定
          
type ls   return   #オプション無しで type コマンドを実行
ls is aliased to `ls -l'  `#エイリアスのみが表示される

type -a ls   return   # -a オプションを指定して実行
ls is aliased to `ls -l'` #外部コマンド
ls is /bin/ls

以下は pwd コマンドの例ですが、このコマンドは両方の形式が用意されています。

type -a pwd   return #全てのタイプを表示
pwd is a shell builtin  #内部コマンド
pwd is /bin/pwd  #外部コマンド

※ 内部コマンドが優先されるので、普通に pwd コマンドを実行すると内部コマンドが実行されます。

実行可能ファイル(executable file)の外部コマンドを実行するには、「/bin/pwd」と絶対パスを指定します。

/bin/pwd  return #外部コマンドの pwd を実行
/Users/foo
which コマンド

複数の同じ名前の外部コマンドがある場合、シェルは環境変数 PATH に設定されているディレクトリを調べて最初に見つかったものを実行します。

which コマンドを使うと、どの外部コマンドが実行されるかを調べることができます。

言い換えると which コマンドは、指定されたコマンドをユーザの環境変数 PATH に設定されているディレクトリを順番に調べて、最初に見つかった実行ファイルを表示します。

以下が書式です。

which [オプション] コマンド名

「which コマンド名」を実行すると指定したコマンドの実行ファイルをフルパスで表示します。

which ls   return
/bin/ls

-a オプションを指定すると、最初に見つかったコマンドだけでなく、もしあれば全ての実行ファイルを表示します。

以下は pwd コマンドを which と type コマンドを使って調べる例です。

type コマンドとは異なり、which コマンドではシェルの内部コマンド(組み込みコマンド)やエイリアスは検索できません。

which pwd  return #which コマンド
/bin/pwd

type pwd   return #type コマンド
pwd is a shell builtin

which -a pwd   return #which コマンド
/bin/pwd

type -a pwd   return #type コマンド
pwd is a shell builtin
pwd is /bin/pwd

シェルの環境設定

シェルはユーザーの入力をカーネルに伝えまたその逆を行うコマンドラインインタープリタですが、同時にプログラミング言語なので変数が使えます。

シェルの変数には現在実行中のシェルだけで有効なシェル変数と、新たなシェルを起動したりシェルから実行したコマンドにも引き継がれる環境変数(動作環境に関わるシェル変数)があります。

シェル変数及び環境変数には動作環境等を設定するために予め組み込まれているものとユーザーが独自に設定できるものがあります。

シェル変数

値を設定

以下はシェル変数に値を設定する書式です。

変数名=値

= の前後にスペース(空白)を入れることはできません。

スペースを入れるとコマンドと引数の区切りと解釈されてしまい、command not found というエラーになります。

以下は userName1 という変数に foo という値(文字列)を設定(代入)する例です。

userName1='foo'   return

userName2 = 'bar'   return #= の前後にスペースを入れるとエラーになる
-bash: userName2: command not found

また、文字列を引用符で囲むことは必須ではありませんが、値にスペースなどの特殊文字を含む場合は、値を引用符で囲む必要があります。

name=Billy Joe   return
-bash: Joe: command not found  $ スペースがあるためエラーになる

name='Billy Joe'   return

以下の場合、変数 name1、name2、name3 の値は同じです。

name1=foo
name2='foo'
name3="foo"

echo $name1
foo

echo $name2
foo

echo $name3
foo

コマンド置換を使ってコマンドの実行結果を変数に代入することもできます。

files=$(ls)   return #ls コマンドの実行結果を変数 files に代入
          
echo $files   return #files の値(ls コマンドの実行結果)を表示
01.txt process.txt sample.csv sample.txt sample2.txt ...

now=`date`   return #date コマンドの実行結果を変数 now に代入(バッククォートを使用)

echo $now return #now の値を表示
2019年 9月 7日 土曜日 09時49分17秒 JST

値の取得(参照)

シェル変数の値を取り出す(参照する)には、変数名の先頭に $ を付けます。

以下は変数を設定して、その値を echo コマンドで表示する例です。

name="foo"   return #変数 name に値 foo を代入
          
echo $name   return #変数 name の値を表示
foo    # $name で参照された値が表示される

変数はダブルクォートの中では展開されますが、シングルクォートの中ではそのままの文字列として表示されます。

echo "$name"   return #ダブルクォート
foo  #値が展開されて表示される

echo "name の値は $name です"   return #ダブルクォート
name の値は foo です  #値が展開されて表示される

echo '$name'   return #シングルクォート
$name  #値が展開されず、文字列として表示される

echo 'name の値は $name です'   return #シングルクォート
name の値は $name です  #値が展開されず、文字列として表示される

echo $name   return #引用符なし
foo

echo name の値は $name です   return #引用符なし
name の値はfoo です

${ }

変数の区切りはスペースやカンマなどで判断されるため、変数の後にスペースなどがないとうまく変数が展開されません。

{ } で変数名を括って ${変数名} のようにすると確実に区別することができます。

usrName='foo'   return  #変数を設定

echo "$usrNameはユーザ名です"   return  #変数の後にスペースがない場合
??ユーザ名です

echo "${usrName}はユーザ名です"   return  #{ } で変数を括る
fooはユーザ名です

echo '${usrName}はユーザ名です'   return  #シングルクォートでは変数は展開されない
${usrName}はユーザ名です

※ 変数名にも tab キーの入力補完が機能します。

長いパスなどをシェル変数に入れておくと便利です。

webDir=/Applications/MAMP/htdocs/webdesignleaves/  return #変数にパスを代入

echo $webDir  return #変数の値(パス)を出力
/Applications/MAMP/htdocs/webdesignleaves/

cd $webDir    return #変数を使ってディレクトリを移動

pwd   return #現在のディレクトリ
/Applications/MAMP/htdocs/webdesignleaves

設定したシェル変数はエイリアス同様、ログアウトするとクリアされます。

変数を削除

変数の定義(変数自体)を削除するには unset コマンドを使います。

変数名の先頭に $ は付けません。

unset 変数名

echo $userName1  return #変数 userName1 の値を出力
foo

unset userName1  return #変数 userName1 を削除(変数名の先頭に $ は付けない)

echo $userName1  return #削除されたため何も表示されない

組み込みシェル変数

あらかじめ登録され利用できるシェル変数や環境変数を組み込みシェル変数と呼びます。

組み込みシェル変数はシェルの動作の設定や現在の設定の確認などに使用されます。

現在どのようなシェル変数が設定されているかを確認するには、set コマンドを引数無しで実行します。

set      return #現在設定されているシェル変数(組み込みと独自に設定した変数)を表示
Apple_PubSub_Socket_Render=/private/tmp/com.apple.launchd.cmcdZ2CoV0/Render
BASH=/bin/bash
BASH_ARGC=()
BASH_ARGV=()
BASH_LINENO=()
BASH_REMATCH=([0]="i")
BASH_SOURCE=()
BASH_VERSINFO=([0]="3" [1]="2" [2]="57" [3]="1" [4]="release" [5]="x86_64-apple-darwin18")
BASH_VERSION='3.2.57(1)-release'
COLUMNS=80
DIRSTACK=()
EUID=501
GROUPS=()
HISTFILE=/Users/foo/.bash_sessions/2A251072-3977-4A22-A0A9-BEA54B2F105C.historynew
HISTFILESIZE=500
HISTSIZE=500
HOME=/Users/foo
HOSTNAME=imac01.local
HOSTTYPE=x86_64
IFS=$' \t\n'
LANG=ja_JP.UTF-8

・・・中略・・・

PS1='\W $ '
PS2='> '
PS4='+ '
PWD=/Users/foo
SHELL=/bin/bash

・・・中略・・・

XPC_SERVICE_NAME=0
_=echo
userName=foo  #独自に設定したシェル変数
webDir=/Applications/MAMP/htdocs/webdesignleaves/  #独自に設定したシェル変数

・・・以下省略・・・

環境変数

シェル変数の中で、シェルの動作環境に関わる変数を環境変数と呼びます。

通常のシェル変数は現在実行中のシェルだけで有効ですが、環境変数は新たなシェルを起動したり、シェルから実行したコマンドにも引き継がれます。

また、環境変数は通常のシェル変数と区別するために、変数名は一般的に全て大文字になっています。

代表的な環境変数には以下のようなものがあります。

環境変数の例(一部)
環境変数 意味
LANG ロケール(言語、地位域名、文字コード)の設定。日本語環境のデフォルトは ja_JP.UTF-8
PATH コマンドが存在するパス(コマンド検索パス)が設定されています。パスを省略してコマンドを実行すると、この環境変数 PATH に定義されているパスを順番に検索し、コマンドが見つかった時点でそのコマンドを実行します。
PS1 シェルのプロンプト(として表示する文字列)の定義
PWD カレントディレクトリ(cd コマンドによりセットされる現在の作業ディレクトリ)
HOME ユーザのホームディレクトリ
TMPDIR 一時ファイルに使用されるディレクトリ
IFS 行をどこでフィールドに分割すべきなのかを判別するための内部フィールド区切り

上記の変数は、組み込みシェル変数でもあります。

環境変数を表示 printenv

環境変数の内容を表示するには printenv コマンドを使います。

以下が書式です。

変数名を指定せずに実行すると環境変数を変数名と内容を対にして一覧表示することができます。

printenv [オプション] [変数名]

以下は変数名を指定せずに実行して、全ての環境変数を一覧表示する例です。

printenv     return #環境変数を一覧表示
TERM_PROGRAM=Apple_Terminal
SHELL=/bin/bash
TERM=xterm-256color
TMPDIR=/var/folders/cX2/XXXXXXXXXXXXXXXXXXXXX05h0000gn/T/
Apple_PubSub_Socket_Render=/private/tmp/com.apple.launchd.cmcdZ2CoV0/Render
TERM_PROGRAM_VERSION=421.2
TERM_SESSION_ID=XXXXXXXX-96XX-4XDE-958D-XXXXXX90
USER=foo
SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.R2bxyQjRzd/Listeners
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
PWD=/Users/foo
LANG=ja_JP.UTF-8
XPC_FLAGS=0x0
XPC_SERVICE_NAME=0
SHLVL=1
HOME=/Users/foo
LOGNAME=foo
_=/usr/bin/printenv

「printenv 変数名」で指定した変数名の内容を表示します。

printenv LANG     return #LANG の内容を表示
ja_JP.UTF-8

echo $LANG     return #echo で表示(変数名の先頭に $ が必要)
ja_JP.UTF-8

環境変数の参照は echo コマンドでも可能です。但し「echo $変数名」のように、変数名の先頭に $ が必要です。

printenv は外部コマンドなので、シェル変数にはアクセスできませんが、echo コマンドは内部コマンドなのでシェル変数と環境変数のどちらにもアクセスすることができます。

環境変数を設定 export

独自の環境変数を設定するには export コマンドを使用します。

以下が export コマンドの書式です。

export [オプション] [変数名[=値]]

以下は export コマンドのオプションです。

オプション 意味
-f シェル関数を参照
-n 環境変数をシェル変数に変える
-p 全てのエクスポートされた変数を一覧表示する

環境変数を設定するには(シェル)変数に値を設定し、export コマンドを実行します。

以下は独自の環境変数 ENV1 を設定する例です。

ENV1="sample env1"    return #独自のシェル変数 ENV1 に値を設定

export ENV1    return #export コマンドで環境変数として設定(シェル変数 ENV1 をエクスポート)

printenv ENV1    return #printenv コマンドで内容を確認
sample env1

echo $ENV1    return #echo コマンドで内容を確認
sample env1

以下のように、「export 変数名=値」の形式で変数の設定と環境変数の設定を同時に行うこともできます。

export ENV1="sample env1"   return #独自の環境変数 ENV1 を設定

環境変数を削除

環境変数をクリア(変数自体を削除)するには、シェル変数と同様、unset コマンドを使います。

変数名の先頭に $ は付けません。

unset ENV1   return #ENV1 を削除

エクスポートされた変数を一覧表示

エクスポートされた変数を一覧表示するには、export コマンドに何も引数を指定せずに実行するか -p オプションを指定(変数名は指定しない)して実行します

export -p   return #または export のみで実行
declare -x Apple_PubSub_Socket_Render="/private/tmp/com.apple.launchd.cmcdZ2CoV0/Render"
declare -x ENV1="sample env1"  #独自の環境変数
declare -x HOME="/Users/foo"
declare -x LANG="ja_JP.UTF-8"
declare -x LOGNAME="foo"
declare -x OLDPWD
declare -x PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
declare -x PWD="/Users/foo"
declare -x SHELL="/bin/bash"
declare -x SHLVL="1"
declare -x SSH_AUTH_SOCK="/private/tmp/com.apple.launchd.R2bxyQjRzd/Listeners"
declare -x TERM="xterm-256color"
declare -x TERM_PROGRAM="Apple_Terminal"
declare -x TERM_PROGRAM_VERSION="421.2"
declare -x TERM_SESSION_ID="X1EXXXXXX-96XA9-XXDE-95X8D-5XXXDS4F990"
declare -x TMPDIR="/var/folders/cX2/XXXXXXXXXXXXXXXXXXX0000gn/T/"
declare -x USER="foo"
declare -x XPC_FLAGS="0x0"
declare -x XPC_SERVICE_NAME="0"

環境変数をシェル変数に変更

export コマンドに -n オプションを指定すると、環境変数をシェル変数に変更することができます。

printenv ENV1  return #環境変数 ENV1 の内容を表示
sample env1

export -n ENV1  return #環境変数 ENV1 をシェル変数に変更

printenv ENV1  return #何も表示されない(シェル変数に変更されたため)

echo $ENV1  return #echo コマンドでは内容を確認可能
sample env1
シェル変数と環境変数の違い

実行中のプログラム(コマンドやシェルなど)のことを「プロセス」と呼び、あるプロセスから起動したプロセスを「子プロセス」と呼びます。

通常のシェル変数は現在実行中のシェル(プロセス)だけで有効ですが、環境変数は新たに起動したシェル(子プロセス)や、シェルから実行したコマンド(子プロセス)にも引き継がれます。

シェルはコマンドを起動する際、環境変数のコピーをそのコマンドに渡しますが、シェル変数は渡しません。そのため、コマンド側では環境変数の内容は参照できますが、シェル変数の内容を参照できません。

変数 概要
シェル変数 通常のシェル変数は、現在実行中のシェルだけで有効。
環境変数 新たに起動したシェルやシェルから実行したコマンド(子プロセス)にも引き継がれる。

以下は通常のシェル変数の場合の例です。別のシェルを起動すると、シェル変数は引き継がれないため値を表示(参照)することができません。

shellVar="shell var"   return #シェル変数を設定

echo $shellVar    return #echo で内容を表示
shell var

bash   return #別のシェル(bash)を起動

echo $ENV1   return #echo で内容を表示(参照)できない


exit   return #別のシェルを終了して元のシェルに戻る
exit

echo $shellVar    return #echo で内容が表示される
shell var

以下は環境変数の場合の例です。別のシェルを起動しても、シェル変数は引き継がるため値を表示(参照)することができます。

export ENVX="Env var"    return #環境変数を設定

echo $ENVX     return #echo で内容を表示
Env var

bash    return #別のシェル(bash)を起動

echo $ENVX     return #echo で内容を表示(参照可能)
Env var

exit    return #別のシェルを終了して元のシェルに戻る
exit

echo $ENVX     return #echo で内容を表示
Env var
環境変数 PATH

PATH はコマンドが置かれている(コマンドの保存先の)ディレクトリを設定する環境変数です。

以下は printenv 及び echo コマンドで PATH の内容を表示する例です。

PATH には、コマンドが保存されているディレクトリ(コマンド検索パス)がコロン(:)区切りで記述されています。

printenv PATH    return
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

echo $PATH    return
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

#この例の場合以下のコマンド検索パスが登録されています。
/usr/local/bin
/usr/bin
/bin
/usr/sbin
/sbin

例えば、現在のユーザ名を表示する whoami コマンドは /usr/bin/whoami として保存されている実行可能形式のファイル(executable file)です。

実行可能形式のファイル(コマンド)は、以下のように絶対パスで指定すると実行できます。

/usr/bin/whoami    return
foo

しかし、通常は絶対パスで指定せずにコマンド名を指定して実行します。

それが可能なのはシェルが指定されたコマンドを、PATH に設定されているコマンド検索パスを順番に調べて見つかった実行ファイルを実行するためです。

言い換えると、コマンド検索パスに入っているコマンドはコマンド名だけで実行することができます。

パスを通す

環境変数 PATH には独自にディレクトリ(コマンド検索パス)を追加することができます。PATH にコマンド検索パスを追加することを「パスを通す」と言います。

例えば、自作したコマンドを ~/bin というディレクトリに配置している場合、~/bin をコマンド検索パスに追加することでコマンド名だけで実行できるようになります。

以下がパスを通す書式です。

export コマンドを使って、環境変数 PATH を再設定しています。

$PATH には現在のコマンド検索パスが入っているので、コロン区切りでパスを追加します。

export PATH=$PATH:パス   #コマンド検索パス($PATH)の末尾に「:パス」を追加

export PATH=パス:$PATH   #コマンド検索パス($PATH)の先頭に「パス:」追加

[注意]$PATH を使わずに「PATH=パス」のように追加するパスを環境変数 PATH に直接代入すると、デフォルトのコマンド検索パスがそのパスのみで上書きされてしまいます。

以下は、PATH の末尾に独自のコマンド検索パス ~/bin を追加する例です。

export PATH=$PATH:~/bin   return

printenv PATH   return #PATH を表示
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/foo/bin

以下は、PATH の先頭に独自のコマンド検索パス ~/sbin を追加する例です。

export PATH=~/sbin:$PATH   return

printenv PATH   return #PATH を表示
/Users/foo/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

※同じ名前のコマンドがある場合は、コマンド検索パスの先頭に記述されている方のコマンドが優先されて実行されます。

※セキュリティ上の理由から、通常はカレントディレクトリ「.」をコマンド検索パスには入れません。

また、パスの設定は、ログアウトするとクリア(初期化)されます。設定を保持するには、「~/.bash_profile」などの環境設定ファイルに設定を記述しておく必要があります。

コマンド実行ファイルの配置

環境変数 PATH は初期状態で /usr/local/bin などのデフォルトのパスがすでに設定されています(但し、/bin や /usr/bin はシステムのコマンドを保存する場所になります)。

そのため、コマンドの実行ファイルをデフォルトのパス(/usr/local/bin)に配置(保存)することで PATH を通さなくてもコマンド名だけで実行することができます。

また、そのようにすれば「~/.bash_profile」などの環境設定ファイルに追記する必要もありません。

ファイルのアクセス権限の関係で(macOS の場合)、/usr/local/bin などにファイルを配置するには sudo コマンドを使う必要があります。

環境設定ファイル

環境変数エイリアスなどの設定は、ターミナルを終了したりログアウトすると初期化(クリア)されてしまいます。

常に使用する設定は、シェルが起動時に読み込む環境設定ファイルに記述しておけば、毎回設定する必要がありません。

bash には、シェルの起動時に読み込まれるいくつかの環境設定ファイルが存在します。

また、シェルがログインシェルとして起動された場合と、コマンドラインから他の bash を直接起動した場合に読み込まれる環境設定ファイルが異なります。

ユーザー固有の環境設定は、~/.bash_profile、もしくは ~/.bashrc というファイルへ必要に応じてユーザが記述することが可能です。

但し、Mac の場合、初期状態では ~/.bashrc や ~/.bash_profile は存在しないので、必要に応じて作成する必要があります。

これらのファイルの使い分けは、以下のようにするのが一般的なようです。

~/.bashrc ログインシェル以外でシェルを起動したときに読み込まれるファイル。シェル起動時に実行する設定(プロンプトやエイリアスの設定など)を記述。
~/.bash_profile ログイン時に読み込まれる(ログインシェルだけに実行される)ファイル。環境変数などログイン時に(一回だけ)実行する設定を記述。

~/.bash_profile は、ログイン時に起動されるログインシェルだけに実行される環境設定ファイルです。

ログインシェルでないシェルを起動した場合、~/.bashrc は実行されますが、~/.bash_profile は実行されません。

一般的には、ログイン時に一度だけ実行したい処理は ~/.bash_profile に記述し、シェルを起動する毎に実行したい処理は ~/.bashrc に記述します。

また、ログイン時には ~/.bashrc は読み込まれないので、 ~/.bashrc にエイリアスなどの設定をした場合は、~/.bash_profile の中で ~/.bashrc を読み込むように設定するのが一般的なようです。

~/.bashrc

ログインシェル以外でシェルを起動したときに読み込まれるファイルで、シェルを起動する毎に実行したい処理を記述します。

以下は、環境設定ファイル ~/.bashrc の記述例です。

プロンプトやエイリアスなどの設定を記述しています。

## .bashrc  
PS1="\W \$ "   ##プロンプトの設定
alias cp='cp -i'  ##エイリアスの設定
alias rm='rm -i'  ##エイリアスの設定
alias mv='mv -i'  ##エイリアスの設定
alias ll='ls -l'  ##エイリアスの設定
alias la='ls -aF'  ##エイリアスの設定
set -o noclobber  ##リダイレクトによる上書き禁止の設定(または set -C)
  • 2行目:プロンプトの環境変数 PS1 でプロンプトに表示する文字列を変更
  • 3行目:cp コマンドでコピー先のファイルが存在している場合に確認をするためのエイリアス
  • 4行目:rm コマンドでファイルを削除する際に確認するためのエイリアス
  • 5行目:mv コマンドで移動先のファイルが存在している場合に確認をするためのエイリアス
  • 6行目:ls -l コマンドを実行するエイリアス
  • 7行目:ls -aF コマンドを実行するエイリアス
  • 8行目:リダイレクトによる上書き禁止。

関連項目

~/.bash_profile

ログイン時に読み込まれる(ログインシェルだけに実行される)ファイルで、環境変数などログイン時に実行する設定を記述します。

また、併せて ~/.bashrc を読み込む設定を記述するのが一般的です。

以下は、環境設定ファイル ~/.bashrc の記述例です。

~/.bashrc の読み込みと環境変数の設定を記述しています。

## .bash_profile 
if [ -f ~/.bashrc ]; then
	source ~/.bashrc
fi
export PATH=$PATH:~/bin   ##環境変数 PATH の設定
  • 2〜4行目:~/.bashrc が存在した場合、source コマンドで ~/.bashrc を読み込む(ログインシェルに反映させる)設定です。これにより、ログインシェルでも ~/.bashrc が実行されます。
  • 5行目:環境変数 PATH の設定で、~/bin をコマンド検索パスに追加しています。
設定ファイルを反映 source

~/.bashrc や ~/.bash_profile を編集したら、変更を反映させるにはログインし直す方法がありますが、誤った設定が記述されていると再ログインできなくなる可能性があります。

source コマンドまたは .(ドット)コマンドを使うとシェルの設定(変更)を即座に反映させることができます。

.bashrc と.bash_profile の変更を反映するために、ターミナルで以下を実行します。

source ~/.bashrc   return
source ~/.bash_profile   return

以下が source コマンドと . コマンドの書式で、オプションはありません。

source ファイル名

. ファイル名

source または . コマンドを実行すると、ファイルに記述されているコマンドを現在のシェルで実行します。

言い換えると、source または . コマンドはファイルに記述されているコマンドを一つずつ入力して実行するのと同じことになります。

以下はエイリアスの設定を ~/.bashrc に追加して、source コマンドで変更を即座に反映させる例です。

alias   return #現在設定されているエイリアスを確認
alias cp='cp -i'
alias la='ls -aF'
alias ll='ls -l'
alias rm='rm -i'

vim ~/.bashrc   return/ # vim で編集してエイリアス mv='mv -i' を追加(編集画面は省略)

source ~/.bashrc   return/ #source コマンドで上記編集による変更を現在のシェルに反映

alias   return #エイリアスを確認
alias cp='cp -i'
alias la='ls -aF'
alias ll='ls -l'
alias rm='rm -i'
alias mv='mv -i' #追加が確認できる

.bashrc はシェルを起動したタイミングで読み込まれるファイルです。

また、ログインした際に読み込まれるファイル .bash_profile には、(一般的には).bashrc を読み込む設定が記述されているので、ログイン時に .bashrc が読み込まれます。

  1. ログイン
  2. ~/.bash_profile の実行
  3. ~/.bashrc の実行

その後、エイリアスや PATH の追加を~/.bashrc に記述すると、既に開いているシェルには追加した内容が反映されていないので、source ~/.bashrc で .bashrc を実行することで設定が反映されます(このため、ログインし直さずに設定を反映できます)。

source コマンドはその時に実行しているシェルでコマンドやスクリプトを実行することができます。

また、source コマンドは実行するコマンドやスクリプトに実行権限は必要ありません。

関連項目:シェルスクリプト/スクリプトの外での変数の使用

ファイルのアクセス権限

Unix 系のシステムでは1台のコンピュータを複数のユーザで使用できるように設計されていて、ユーザごとにアクセスできるディレクトリやファイル(実行できるコマンド)など「どのユーザの権限で何ができるか」が決められています。

そして、ユーザの権限などを効率よく管理するために「グループ」が用意され、「ユーザ」は「グループ」に属するようになっています。

また、あらゆる権限が与えられているユーザを「スーパーユーザ」や「root ユーザ」と呼びます。

一般的な UNIX 系のシステムでは root というユーザー名でログインすると、あらゆるコマンドの実行やファイル操作ができるようになりますが、macOS では root でログインしたり、一時的に root に移行して操作を行ったりすることがデフォルトではできないようになっています。

スーパーユーザの権限が必要な操作を行うには、管理者として登録されているユーザが sudo コマンドを使用して一時的にスーパーユーザの権限を取得してコマンドを実行します。

sudo スーパーユーザ権限で実行

sudo コマンドを使うと、パッケージのインストールや Web サーバーの起動などスーパーユーザの権限(root 権限)が必要なコマンドを実行することができます。

以下が書式です。

sudo [オプション] [コマンド]

macOS では sudo コマンドが実行できるのは、管理者として登録されているユーザーだけになります。また、実行する際は自分のパスワードを入力する必要があります。

例えば、sudo の設定ファイル「/etc/sudoers」はスーパーユーザにしか読み書きできないため普通に less コマンドで表示しようとするとエラーになります。

less /etc/sudoers    return 
/etc/sudoers: Permission denied   #権限がないためエラー

以下は sudo コマンドを使って実行する例です。

sudo less /etc/sudoers    return 
Password:  #パスワードを求められるので入力して return を押す

#less コマンドが実行され /etc/sudoers が表示される
#
# Sample /etc/sudoers file.
#
# This file MUST be edited with the 'visudo' command as root.
#
# See the sudoers man page for the details on how to write a sudoers file.

##
# Override built-in defaults
##
Defaults        env_reset    
・・・以下省略・・・

また、sudo コマンドに -u オプションとユーザを指定すると、指定したユーザとしてコマンドを実行することができます。

sudo -u ユーザ名 コマンド

以下はユーザ foo として ls コマンドを実行する例です。

sudo -u foo ls /Users/foo    return 
Password:  #パスワードを求められるので入力して return を押す(以下は実行結果の例)
Desktop		Downloads	Movies		Pictures	fooDir
Documents	Library		Music		Public

su コマンド

su コマンドを使って、別のユーザとしてコマンドを実行することもできます。

以下は su コマンドで一時的にユーザ foo としてコマンドを実行し、exit コマンドで元のユーザに戻る例です。以下の例では現在のユーザのプロンプトは「~ $ 」です。

~ $ su foo    return 
Password:  #パスワードを入力して return 

bash-3.2$    #プロンプトが変わる
bash-3.2$ ls ~   return  #ユーザ foo としてコマンドを実行
Desktop		Downloads	Movies		Pictures	fooDir
Documents	Library		Music		Public
bash-3.2$ exit  return  #ユーザ foo を終了
exit
~ $    #プロンプトが元に戻る

su コマンドは、ログインし直さずに他のユーザーに切り替えるコマンドです。

su は「Substitute User」の意味です。

以下が書式です。

su [オプション] ユーザー

一般的な Unix システムでは「スーパーユーザー( root ユーザー)」の権限でシェルを起動してマンドを実行する場合に使用しますが、macOS では root でログインしたり、一時的に root に移行して操作を行うことがデフォルトではできないようになっています。

そのため、ユーザを省略してスーパーユーザに移行しようとするとパスワードの入力は求められますが、管理者として登録されているユーザーのパスワードを入力してもデフォルトでは以下のように「su: Sorry」と表示され移行することができません。

su   return 
Password:  #パスワードを入力して return 
su: Sorry

Apple 関連ページ:Mac でルートユーザを有効にする方法やルートパスワードを変更する方法

/etc/sudoers

「/etc/sudoers」には、sudo コマンドを使ってどのユーザーがどんなコマンドを使用できるかが設定されています。

また、「/etc/sudoers」は専用の visudo コマンドを使って編集する必要があります。

sudoers の書式は以下のようになっています。

ユーザー ホスト=(権限) コマンド

例えば、「foo ALL=(bar) ls」はユーザ「foo」は全てのホスト(ALL)で「bar」として(bar の権限で)「ls」コマンドを実行できるというような意味になります。

sudoers の最後の方に以下のような記述があります。これは「root」と「%admin(admin グループ)」に対して、全ての権限(全てのホストで全てのコマンドを実行する権限)を与える設定です。

##
# User specification
##

# root and users in group wheel can run anything on any machine as any user
root            ALL = (ALL) ALL
%admin          ALL = (ALL) ALL

パーミッション

Unix 系のシステムでは、全てのファイルやディレクトリに対して「所有者」「所有グループ」「その他のユーザ」(パーミッションを与える対象)ごとに、「読み出し(r)」「書き込み(w)」「実行(x)」の権限(パーミッション)を設定することができます。

初期状態では「所有者」はファイルやディレクトリの作成者、「所有グループ」は所有者のデフォルトのグループ(プライマリグループ:ログイン時のグループ)になります。

パーミッションの確認 ls -l

ファイルやディレクトリに設定されているパーミッションを確認するには、ls コマンドに詳細表示をする -l オプションを指定して実行します。

以下は root ディレクトリで ls -l コマンドを実行した際のスクリーンショットです。パーミッションや所有者、所有グループなどの情報が表示されます。

root ディレクトリで ls -l コマンドを実行した際のスクリーンショット

左側の「rwxrwxr-x」や「rw-r-xr-x」の9文字の部分がパーミッションを表します。

root ディレクトリで ls -l コマンドを実行した際のスクリーンショット

パーミッションを表す9文字は、3文字ごとに「所有者」「所有グループ」「その他のユーザ」に対するアクセス権を表しています。

左端の d や - はファイルタイプを表す文字で、d はディレクトリ、- はファイル、l はシンボリックリンクを意味します。

右端の + は拡張セキュリティ情報(ACL など)、@ は拡張属性が設定されていることを表しています。

以下はアクセス権(パーミッション)の文字(シンボル)の意味及び数値で表した場合の値です。

パーミッション
文字 意味 数値
r 読み出し可能(read) 4
w 書き込み可能(write) 2
x 実行可能(excute) 1
- アクセス権なし 0

同じパーミッションでもファイルとディレクトリでは実行できることが違います。

例えば、ファイルの書き込み(w)が許可されていなくても、ファイルの削除や名前の変更はできてしまいます。

ファイルの削除や名前の変更は、ファイルが保存されているディレクトリの書き込み(w)が許可されているかどうかによります。

ファイルとディレクトリのパーミッション
  ファイルのパーミッション ディレクトリのパーミッション
読み出し(r) ファイルの内容を表示できる ディレクトリの一覧を表示できる
書き込み(w) ファイルにデータを書き込める ディレクトリの一覧を書き換えられる
  • ディレクトリに新規ファイルを作成できる
  • ファイルを削除できる
  • ファイルの名前を変更できる
実行(x) ファイルをコマンドとして実行できる 配下のディレクトリに移動できる

以下は foo というユーザで touch コマンドで test.txt というファイルを作成して、ls -l コマンドでパーミッションを確認する例です。

所有者はファイルの作成者 foo で、所有グループはユーザ foo が作成された際に自動的に所属したプライマリグループ staff になっています。

所有者(foo)のパーミッションは「rw-」で、所有グループ(staff)及びその他のユーザのパーミッションは「r--」が設定されているのがわかります。

これは所有者は書き込みができますが、所有者以外は読み出しのみが可能であることを意味します。

新規に作成するファイルのパーミッションは、umask コマンドで設定されています。

touch test.txt    return #ファイルを作成
ls -l test.txt    return  #パーミッションを確認
-rw-r--r--  1 foo  staff  0  9 18 19:50 test.txt

以下は foo というユーザで mkdir コマンドで test というディレクトリを作成して、ls -ld コマンド(ディレクトリなので -d を併用)でパーミッションを確認する例です。

所有者(foo)のパーミッションは「rwx」で、所有グループ(staff)及びその他のユーザのパーミッションは「r-x」が設定されているのがわかります。

これは所有者はディレクトリ内でのファイルの作成、削除、変更が可能ですが、所有者以外はディレクトリの一覧表示やディレクトリへの移動のみが可能であることを意味します。

mkdir test    return #ディレクトリを作成
ls -ld test    return  #パーミッションを確認
drwxr-xr-x  2 foo  staff  64  9 18 19:50 test

以下は ls コマンドのパーミッションを確認する例です。

所有者は root、所有グループは wheel で、「所有者」「所有グループ」「その他のユーザ」の全てに「x(実行可能)」が設定されていて全てのユーザが ls コマンドを実行できるのがわかります。

ls -l /bin/ls   return 
-rwxr-xr-x  1 root  wheel  38704  5  4 14:26 /bin/ls

Public ディレクトリ

macOS では各ユーザのホームディレクトリに Public というディレクトリ (フォルダ)があります。

以下はユーザ foo の Public ディレクトリのパーミッションを確認する例です。

Public はファイル共有用のディレクトリで、全てのユーザに「読み出し(r)」と「実行(x)」が許可されていて閲覧可能になっています。

ls -ld Public/   return 
drwxr-xr-x+ 4 foo  staff  128  7 29 19:02 Public/

また、Public の中には Drop Box という特別なディレクトリがあり、全てのユーザに「書き込み(w)」と「実行(x)」が許可されていてファイルを保存できるようになっています。

但し、所有者以外に「読み出し(r)」は許可されていないので、所有者以外はディレクトリの中身を見ることはでないようになっています。

cd Public/   return #Public へ移動
ls -l   return
total 0
drwx-wx-wx+ 3 foo  staff  96  7 29 19:02 Drop Box

パーミッションの変更 chmod

ファイルやディレクトリのパーミッションを変更するには chmod コマンド(change file modes)を使用します。

chmod コマンドは、そのファイルやディレクトリの所有者またはスーパーユーザ(root ユーザ)だけが実行できます。

以下が書式です。

chmod [オプション] パーミッション ファイル1 ファイル2 ...

以下は主なオプションです。

オプション 意味
-R ディレクトリ内のファイルとディレクトリを再帰的に変更する
-f 変更できなかった場合にエラーメッセージを表示しない
-v 処理した内容(ファイル名やディレクトリ名)を出力する

chmod でパーミッションを変更(指定)するには「数値」を使用する方法と、「文字(シンボル)」を使用する2つの方法があります。

数値でパーミッションを指定

「所有者」「所有グループ」「その他のユーザ」に対して、それぞれ8進数の数値(通常は3桁の数値)でパーミッションを指定します。

suid(set-user-ID)、sgid(set-group-ID)、sticky bit などの特殊なパーミッションを設定する場合は4桁の数値で指定します。

各パーミッションの値は以下のようになっていて、実際に指定する場合は許可するパーミッションの数値を合計した値を指定します。

パーミッションの数値
パーミッション 数値
読み出し可能(r) 4
書き込み可能(w) 2
実行可能(x) 1
アクセス権なし(-) 0

3文字のパーミッションのそれぞれをアクセス権がある部分を1、ない部分を0とする2進数にすると、例えば「rwx」は「111」、「rw-」は「110」、「r-x」は「101」のようになります。

そしてそれらの3桁の2進数を8進数に変換して、パーミッションの数値としています。

パーミッション 2進数 8進数
rwx 111 7
rw- 110 6
r-x 101 5
r-- 100 4
-wx 011 3
-w- 010 2
--x 001 1
--- 000 0

「所有者」「所有グループ」「その他のユーザ」のそれぞれに対して指定するパーミッションの組み合わせは下記の8通りになります。

パーミッション 数値 意味
「読み出し」と「書き込み」と「実行」(全て)を許可(rwx) 7 4 + 2 + 1
「読み出し」と「書き込み」を許可(rw-) 6 4 + 2 + 0
「読み出し」と「実行」を許可(r-x) 5 4 + 0 + 1
「読み出し」のみを許可(r--) 4 4 + 0 + 0
「書き込み」「実行」を許可(-wx) 3 0 + 2 + 1
「書き込み」のみを許可(-w-) 2 0 + 2 + 0
「実行」のみを許可(--x) 1 0 + 0 + 1
全て許可しない(---) 0 0 + 0 + 0

パーミッションは左から「所有者」「所有グループ」「その他のユーザ」なので、それぞれに上記のパーミッションを数値で指定します。

例えば、「所有者」に全てを許可(7)し、「所有グループ」と「その他のユーザ」には「読み出しと実行」を許可(5)する場合は「755」と指定します。

以下はカレントディレクトリに test というディレクトリを作成し、パーミッションを「700(所有者以外はアクセスを許可しない)」に変更する例です。

mkdir test   return #ディレクトリを作成
ls -ld test   return #パーミッションを確認
drwxr-xr-x  2 foo  staff  64  9 19 17:04 test #755
chmod 700 test   return #パーミッションを 700 に変更
ls -ld test   return #パーミッションを確認
drwx------  2 foo  staff  64  9 19 17:04 test #700

文字でパーミッションを指定

以下は、文字(シンボル)でパーミッションを指定する場合の書式です。

chmod [対象ユーザ] オペレータ パーミッション ファイル名

対象ユーザ
意味 文字(シンボル)
所有者(user) u
所有グループ(group) g
その他のユーザ(othres) o
全て(all) a

対象を指定しなかった場合も全てのユーザーが対象となりますが、その場合はマスク値(umask)の影響を受けます。

オペレータ
意味 文字(シンボル)
権限を追加 +
権限を削除 -
権限を設定(元の設定をクリアして権限を指定) =

パーミッション
意味 文字(シンボル)
読み出し(read) r
書き込み(write) w
実行(execute) x

以下はファイル test.txt のその他のユーザ(o)の「読み出し(r)」の許可を削除(-)する例です。

ls -l test.txt    return #パーミッションを確認
-rw-r--r--  1 foo  staff  0  9 18 19:50 test.txt  #644
chmod o-r test.txt    return #その他のユーザの読み出しのパーミッションを削除
ls -l test.txt    return #パーミッションを確認
-rw-r-----  1 foo  staff  0  9 18 19:50 test.txt  #640

以下はファイル test.txt の所有グループ(g)の「書き込み(w)」の許可を追加(+)する例です。

ls -l test.txt    return #パーミッションを確認
-rw-r-----  1 foo  staff  0  9 18 19:50 test.txt  #640
chmod g+w test.txt    return #所有グループに書き込みのパーミッションを追加
ls -l test.txt    return #パーミッションを確認
-rw-rw----  1 foo  staff  0  9 18 19:50 test.txt  #660

以下はファイル test.txt のその他のユーザ(o)の「読み出し(r)」の許可を設定(=)する例です。

ls -l test.txt    return #パーミッションを確認
-rw-rw----  1 foo  staff  0  9 18 19:50 test.txt  #660
chmod o=r test.txt     return #その他のユーザの読み出しのパーミッションを設定
ls -l test.txt    return #パーミッションを確認
-rw-rw-r--  1 foo  staff  0  9 18 19:50 test.txt   #664

以下はファイル test.txt の全てのユーザ(a)の「実行(x)」の許可を追加(+)する例です。

ls -l test.txt    return #パーミッションを確認
-rw-r--r--  1 foo  staff  0  9 25 10:02 test.txt
chmod a+x test.txt    return #全てのユーザに実行のパーミッションを追加
ls -l test.txt    return #パーミッションを確認
-rwxr-xr-x  1 foo  staff  0  9 25 10:02 test.txt

対象を指定しなかった場合も全てのユーザーが対象となりますが、全てのユーザ(a)を対象に指定する場合と異なり、マスク値(umask)の影響を受けます。

ls -l test2.txt    return #パーミッションを確認
-rw-r--r--  1 foo  staff  0  9 25 10:03 test2.txt

umask   return #マスク値を確認
0022 #所有グループとその他のユーザの書き込み(w)の権限を制限する設定(デフォルト)

chmod +w test2.txt    return #対象を指定せずに書き込み(w)のパーミッションを追加
ls -l test2.txt   return #パーミッションを確認
-rw-r--r--  1 foo  staff  0  9 25 16:00 test2.txt #umask の影響を受け、所有グループとその他のユーザには書き込みのパーミッションは追加されない

chmod a+w test2.txt    return #全てのユーザ(a)に書き込みのパーミッションを追加
ls -l test2.txt    return #パーミッションを確認
-rw-rw-rw-  1 foo  staff  0  9 25 16:00 test2.txt #全てのユーザに書き込みのパーミッションが追加される

touch test3.txt   return #test3.txt を新規作成
ls -l test3.txt    return #パーミッションを確認
-rw-r--r--  1 foo  staff  0  9 25 16:10 test3.txt

chmod +x test3.txt    return #対象を指定せずに実行(x)のパーミッションを追加
ls -l test3.txt    return #パーミッションを確認
-rwxr-xr-x  1 foo  staff  0  9 25 16:10 test3.txt #umask で実行は制限されていないので、全てのユーザに実行のパーミッションが追加される

対象ユーザは複数指定することができます。

以下はファイル test.txt の所有者(u)と所有グループ(g)に「実行(x)」の許可を追加(+)する例です。

ls -l test.txt    return #パーミッションを確認
-rw-rw-r--  1 foo  staff  0  9 18 19:50 test.txt   #664
chmod ug+x test.txt  #所有者と所有グループに実行のパーミッションを追加
ls -l test.txt    return #パーミッションを確認 
-rwxrwxr--  1 foo  staff  0  9 18 19:50 test.txt   #774

以下の3つのコマンドは、いずれも同じことになります。

カンマの後にスペースを入れるとエラーになります。

chmod 755 test.txt
chmod u=rwx,go=rx test.txt
chmod u=rwx,go=u-w test.txt

-R オプションを指定して chmod コマンドを実行するとディレクトリ以下のファイルやディレクトリをまとめてパーミッションを変更することができます。

ls -ld test   return #現在の test ディレクトリのパーミッションを確認
drwxr-xr-x  5 foo  staff  160  9 20 08:15 test  #755
ls -l test/  return #現在の test ディレクトリ配下のパーミッションを確認
total 0
-rw-r--r--  1 foo  staff   0  9 20 08:15 abc.txt #644
drwxr-xr-x  2 foo  staff  64  9 20 08:15 testDir #755
-rw-r--r--  1 foo  staff   0  9 20 08:15 xyz.txt #644

chmod -R o-r test/  return #test ディレクトリ及び配下の「その他のユーザ」の読み出し権限を削除

ls -ld test   return # test ディレクトリのパーミッションを確認パーミッションを確認
drwxr-x--x  5 foo  staff  160  9 20 08:15 test  #751
ls -l test/   return # test ディレクトリ配下のパーミッションを確認
total 0
-rw-r-----  1 foo  staff   0  9 20 08:15 abc.txt  #640
drwxr-x--x  2 foo  staff  64  9 20 08:15 testDir  #751
-rw-r-----  1 foo  staff   0  9 20 08:15 xyz.txt  #640

デフォルトのパーミッション設定 umask

ファイルやディレクトリを作成する際にデフォルトで適用されるパーミッションは、umask コマンドで設定(または確認)することができます。

umask はシステムの管理ポリシーによりデフォルトの値が設定されています。

macOS の場合、umask の設定値は「022」となっています。実際には4桁で「0022」と表示されます。

umask  return #現在の umask の値を確認
0022

umask コマンドは bash の内部コマンドなので、help コマンド(help umask)でも概要を表示できます。

以下が umask コマンドの書式です。

umask [オプション] [値(mode)]

以下が主なオプションです。

オプション 意味
-p 値を指定しない場合、現在の設定値を「umask 値」の形式で表示
-S 値を指定しない場合、現在の設定値をシンボルモード(rwx 等)で表示
umask -p   return # -p オプションを指定して現在の umask の値を確認
umask 0022

umask -S   return # -S オプションを指定してシンボルモードで表示
u=rwx,g=rx,o=rx

Unix 系のシステムではファイルやディレクトリを作成する際のパーミッションは制限が最も少ない状態(ファイル:666、ディレクトリ:777)になっています。

しかし、この状態では全てのユーザーから書き込みや削除が可能になってしまうため、umask を使ってパーミッションを制御(制限)するようになっています。

umask で指定(設定)するのはパーミッションそのものではなく,許可しないビットを指定ます。

chmod コマンドと同じように8進数を利用しますが、umask の場合は許可を削除(アクセスを禁止)するビットを「1」として指定します。

例えば、全てを許可する(何も禁止しない)場合は、000 000 000 で umask は「000」、その他のユーザの全てのアクセスを禁止するには 000 000 111 となり umask は「007」を指定することになります。

初期状態の umask の値 022 は 000 010 010 なので所有グループとその他のユーザから書き込み(w)の権限を削除しています。

umask の値からパーミッションを算出するには、以下のようにします。以下は umask の値が 022 の場合の例です。

  • ファイルの場合: 666 - 022 = 644
  • ディレクトリの場合: 777 - 022 = 755

以下は、ファイルやディレクトリを新規作成する場合、umask コマンドを使ってその他のユーザには全てのアクセスを許可しないようにする例です。

umask 007   return # umask を設定
umask   return # umask の値を確認 
0007
umask -S   return #umask の値を -S を指定して確認 
u=rwx,g=rwx,o= #その他のユーザの権限なし

touch umask_test.txt   return # ファイルを作成
mkdir umask_testDir   return # ディレクトリを作成
ls -l   return # パーミションを確認
total 304
-rw-r--r--  1 foo  staff    753  8 26 10:50 01.txt #既存のファイルは変更されない
drwxr-xr-x  5 foo  staff    160  8 22 09:52 barDir #既存のディレクトリは変更されない
・・・中略・・・
-rw-rw----  1 foo  staff      0  9 20 11:04 umask_test.txt #その他のユーザの権限なし
drwxrwx---  2 foo  staff     64  9 20 11:04 umask_testDir #その他のユーザの権限なし

なお、umask コマンドは新規に作成するファイルやディレクトリのパーミッションに適用されるので、既存のファイルやディレクトリのパーミッションを変更するには chmod コマンドを使用します。

また、ログアウトすると umask コマンドで設定した値は初期化されます(もとに戻ります)。

アクセスコントロールリスト ACL

ACL を使うと、通常のパーミッションではできない細かなアクセス制御が可能になります。

ACL(Access Control Lists)はアクセスを制御するリストというような意味で、アクセスを制御する設定の登録(ACE:Access Control Entry)の集まり(リスト)です。

ACL が設定されているディレクトリやファイルは ls -l コマンドで確認すると表示される一覧のパーミッションの右端に「+」が付いています。但し、同時に拡張属性が設定されている場合は、「+」は表示されず拡張属性を表す「@」が表示されます。

以下はユーザ foo のホームディレクトリで ls -l コマンドを実行した例です。

Desktop や Documents、Downloads などのデフォルトで用意されているディレクトリのパーミッションの右端に「+」が表示されていて、ACL が設定されているのが確認できます。

ls -l   return
total 0
drwx------+  3 foo  staff   96  9 20 15:46 Desktop
drwx------+  3 foo  staff   96  9 20 15:46 Documents
drwx------+  3 foo  staff   96  9 20 15:46 Downloads
drwx------+ 27 foo  staff  864  9 20 15:46 Library
drwx------+  3 foo  staff   96  9 20 15:46 Movies
drwx------+  3 foo  staff   96  9 20 15:46 Music
drwx------+  3 foo  staff   96  9 20 15:46 Pictures
drwxr-xr-x+  4 foo  staff  128  9 20 15:46 Public
drwxr-xr-x   2 foo  staff   64  9 20 15:51 fooDir

ACL の内容を確認するには、ls コマンドに -le オプションを指定して実行します。

ls -le /Users/foo   return
total 0
drwx------+  3 foo  staff   96  9 20 15:46 Desktop
 0: group:everyone deny delete
drwx------+  3 foo  staff   96  9 20 15:46 Documents
 0: group:everyone deny delete
drwx------+  3 foo  staff   96  9 20 15:46 Downloads
 0: group:everyone deny delete
・・・以下省略・・・

例えば、上記の Desktop などの ACL には「0: group:everyone deny delete」という ACE が1つ登録されていて、これは「所有者を含む全てのユーザ(group:everyone)にディレクトリの削除(delete)を禁止(deny)する」という内容です。

macOS では Desktop や Applications などの重要なディレクトリが削除されないように OS によって ACL が設定されています。

ACL の設定は、Finder でフォルダを選択して右クリックで「情報を見る」を選択して確認することもできます。

「共有とアクセス権」で「カスタムアクセス権が割り当てられています」と表示されている場合は ACL のアクセス権が設定されています。

Finder でフォルダを選択して右クリックで「情報を見る」を選択際のスクリーンショット

ACE の設定

ACL の個々のアクセス権(の登録)を ACE(Access Control Entry:アクセスコントロールエントリ)と呼びます。

ACL(Access Control Lists)は ACE のリストのことで、各 ACE に特定のユーザーやグループのアクセス権の定義が設定(登録)されています。

macOS では、ACE を設定するのに chmod コマンドを使用します。

ACE の設定(chmod コマンド)は、そのファイルやディレクトリの所有者またはスーパーユーザ(root ユーザ)だけが実行できます。

ACE を設定する場合は +a オプションを、解除する場合は -a オプションを使用します。

以下が書式です。

ACE を設定する場合: chmod +a "ACE の内容" ファイル(またはディレクトリ)

ACE を解除する場合: chmod -a "ACE の内容" ファイル(またはディレクトリ)

"ACE の内容" は、アクセス権限を許可するには allow を使い、禁止するには deny を使って以下のように指定します。

許可する場合: ユーザ(またはグループ) allow 権限

禁止する場合: ユーザ(またはグループ) deny 権限

以下は、カレントディレクトリのファイル sample.txt に、ユーザ「bar」に書き込み(write)許可(allow)の、ユーザ「foo」に削除(delete)禁止(deny)の ACE を設定(+a)する例です。

chmod +a "bar allow write" sample.txt   return #ACL(ACE)を設定
ls -le sample.txt   return #ACL(ACE のリスト)を確認
-rw-r--r--+ 1 xxxx  staff  519  9  9 08:47 sample.txt
 0: user:bar allow write  #ACE 

chmod +a "foo deny delete" sample.txt   return #ACL(ACE)を設定
ls -le sample.txt   return #ACL を確認
-rw-r--r--+ 1 xxxx  staff  519  9  9 08:47 sample.txt
 0: user:foo deny delete  #ACE(新たに追加されたエントリ)
 1: user:bar allow write  #ACE

以下は前述の例で設定した ACL(ACE)を解除(-a)する例です。

chmod -a "bar allow write" sample.txt   return #ACL(ACE)を解除
chmod -a "foo deny delete" sample.txt   return #ACL(ACE)を解除
ls -le sample.txt  return #ACL を確認(ACE は全て解除され、リストは表示されない)
-rw-r--r-- 1 xxxx  staff  519  9  9 08:47 sample.txt

ACL を全て解除するには -N オプリョンを指定します。

以下はディレクトリ test に ACL を設定して、その後、全て解除する例です。

chmod +a "foo allow write" test   return #ACL(ACE)を設定
chmod +a "bar allow write" test   return 
chmod +a "everyone deny delete" test   return 

s -lde test  return #ACL を確認(ディレクトリなので d オプションも指定)
drwxr-x--x+ 5 xxxx  staff  160  9 20 08:15 test
 0: group:everyone deny delete
 1: user:bar allow add_file
 2: user:foo allow add_file

chmod -N test  return #ACL(ACE)を全て解除

ls -lde test  return #ACL を確認(削除されているので表示されない)
drwxr-x--x  5 xxxx  staff  160  9 20 08:15 test    

シェルスクリプト

シェルに実行させたい操作(コマンドやオプション、制御構文など)をテキストファイルにまとめて記述したものを「シェルスクリプト」と呼びます。

よく使う処理や定期的に実行する処理などをシェルスクリプトとして保存しておけば、簡単に呼び出して実行することができます。

シェルスクリプト自体は単なるテキストファイルで、「vim」や「nano」などのテキストエディタを使って作成・編集することができます。GUI のテキストエディタを使うこともできます。

以下は、macOS の標準シェルである bash によるシェルスクリプトの基本的なことについてになります。

シェルスクリプトの実行

シェルスクリプトは「bash ファイル名(パス)」のように、スクリプトを処理する実行コマンド(bash コマンド)と、スクリプトが記述されたファイルのパスを指定することで実行することができます。

シェルスクリプトが実行される際は、別のシェルが新たに起動してそのシェルで実行され、シェルスクリプトが終了するとそれを起動した元のシェルに戻ります。

最もシンプルなシェルスクリプトはコマンドを記述しただけのテキストファイルで、例えば以下のようにコマンドを記述したファイルを作成して実行することができます。

以下は「hello」というファイルを作成し、「echo "Hello, $(whoami)"」というコマンドを記述して、bash コマンドで記述したスクリプトを実行する例です。

複数行に渡るコマンドや制御構文を記述する場合は、vimnano、または GUI のテキストエディタを使いますが、以下の例の場合はcat コマンドとリダイレクトで作成しています。

cat > hello  return #hello というファイルに以下を記述
echo "Hello, $(whoami)"
control + d #cat を終了して記述した内容を保存

cat hello  return #hello というファイルの内容を確認(念の為)
echo "Hello, $(whoami)"

bash hello  return #bash コマンドでシェルスクリプト hello を実行
Hello, foo #コマンドが実行されて出力される(カレントユーザが foo の場合)

ファイルの拡張子

シェルスクリプトのファイルには「.sh」のような拡張子を付けることができますが、拡張子は付けない方が(拡張子を指定する必要がないので)コマンドとして実行しやすいです。

スクリプト名だけで実行

bash コマンドを使わずに、スクリプト名だけで処理を実行できるようにするには以下のようにします。

  • スクリプトの1行目に実行コマンド(#!/bin/bash)を指定
  • chmod コマンドを使ってファイルに実行の権限を設定(実行可能属性を追加
  • コマンド検索パスに登録されている場所に配置(またはパス付きで実行)

以下は、「hellotime」というファイルを作成し、コマンドを記述して bash コマンドを使わずにスクリプト名だけで処理を実行できるようにする例です。

テキストエディタで以下の内容のファイルを作成しという名前でカレントディレクトリ(ホームディレクトリ)に保存します。

hellotime
#!/bin/bash
echo "Hello, `whoami` !"   #または echo "Hello, $(whoami) !"
date "+It's %H:%M, now."

1行目は実行コマンド bash の指定(#!/bin/bash)です。

※スクリプト名だけで実行できるようにするには、スクリプトの先頭行で「#!」に続けてスクリプトを実行するコマンドのフルパスを指定します。

この記述により新たに別の bash が起動して処理を実行します。

「#!」とコマンドのパスの間にスペースはあってもなくても大丈夫です。

#! スクリプトを実行するコマンドのフルパス(1行目に記述)

スクリプトを実行するコマンド bash のフルパスは「/bin/bash」になります(コマンドのパスは type which コマンドで確認できます)。

2行目と3行目はこのシェルスクリプトの内容です。

2行目は echo コマンドを使って「Hello カレントユーザ名!」と出力する記述です。コマンド置換 $() または `` を使ってコマンド名「whoami」をコマンドの出力に置き換えています。

※シェルスクリプトの場合は、互換性を考慮して `` を使うのが一般的なようです。

3行目は date コマンドで + を指定して現在の時刻をフォーマット文字列で出力しています。

念の為ターミナルで以下を実行してスクリプトが正しく機能することを確認します。

bash hellotime   return #bash コマンドでシェルスクリプト hellotime を実行
Hello, xxxx !  #xxxx はカレントユーザ名
It's 19:01, now.

コマンドとして実行できるように chmod コマンドでファイル hellotime に実行権限を設定します。

以下の例では全てのユーザ(a)に実行(x)を許可するように「a+x」を指定しています(自分だけに許可する場合は u+x)。

chmod a+x hellotime   return #ファイルに実行権限を設定
ls -l hellotime   return #パーミッションを確認
-rwxr-xr-x  1 xxxx  staff  63  9 21 16:55 hellotime

実行権限を設定したので、シェルスクリプトのパスを指定すれば実行することができます。

hellotime がカレントディレクトリにある場合は、カレントディレクトリ「.」からパスを指定します。

./hellotime  /return #パスを指定してシェルスクリプトを実行
Hello, xxxx !
It's 19:12, now.

パスを指定せず、スクリプト名だけで実行するにはファイルをコマンド検索パスに登録されている(パスの通っている)ディレクトリに配置します。

自分専用の場合はホームディレクトリ下に「bin」ディレクトリ(~/bin)を作成して、パスを通して.bash_profile などに記述しておきます。

以下は mkdir コマンドでホームディレクトリに「bin」ディレクトリを作成し、mv コマンドでスクリプト hellotime を移動する例です。

~/bin にパスが通って入れば、以下のようにスクリプト名だけで実行することができます。

mkdir ~/bin   /return #ホームディレクトリに bin ディレクトリを作成
      
mv hellotime ~/bin/   return #作成した ~/bin へファイルを移動

hellotime  return #スクリプト名だけで実行
Hello, xxxx !
It's 19:59, now

他のユーザーも実行する場合は「/usr/local/bin」に配置するのが一般的です。

「/bin」や「/usr/bin」はシステムのコマンドを保存する場所になります。

以下は sudo mv コマンドを使ってファイルをに移動する例です(ディレクトリに書き込みができるのは root のみ)。

sudo mv hellotime /usr/local/bin/  return #ファイルを移動
Password:     #パスワードを入力して return

#書き込み権限があるのは root のみ
ls -ld /usr/local/bin
drwxr-xr-x  10 root  wheel  320  9 22 08:39 /usr/local/bin

改行・空白文字・引用符・コメント

シェルスクリプトでは「読みやすく」するため、適宜空白や空行を入れたりすることができます。

改行

シェルスクリプトは、「行単位」で処理されます。

基本的には、コマンドラインで return を押すところで改行します。

また、処理のまとまりごとに改行(空行)を入れて読みやすくすることができます。

挿入された空行は実行結果に影響はありません。

空白文字(半角スペースやタブ)

空白文字はコマンドラインの入力方法と同様、コマンドや引数の区切りとして扱われます(タブはコマンドラインでは別の意味を持ちますが)。

空白文字は1つでも複数でも動作は同じで、行頭や行末に入れても問題ありません。

空白文字を入れて文字の位置を揃えたりすることができます。

引用符

シェルスクリプトの中のダブルクオート(")やシングルクオート(')、エスケープ文字(\)は、コマンドラインと同じように機能します。

シェルにとって特別な意味を持つ記号(ワイルドカードの * など)は、引用符(" ')やバックスラッシュ(\)を使って必要に応じてエスケープします。

ダブルクォートの中では $ を使った変数やコマンド置換(バッククォート ` も)は展開されます。

コメント

シェルスクリプトの中では「#」以降がコメントとなります。

コメントは行頭からでも行の途中からで記述することができます。

#!/bin/bash 	 ###1行目には実行コマンドを記述
echo "Hello, `whoami` !"  #カレントユーザを出力
date "+It's %H:%M, now."  #時刻を出力
#コメント
###コメント
###########コメント##########

変数

シェルスクリプトの中で変数を使う場合は、「変数名=値」の形式で定義します(= の前後に空白を入れることはできません)。

定義した変数の値は「$変数名」または「${変数名}」で参照することができます。

既存のシェル変数環境変数もシェルスクリプトの中で参照することができます。

例えば、以下のようなシェルスクリプト(myscript)をカレントディレクトリに作成します。

#!/bin/bash
myvar="my variable"    #変数の定義(空白を含むのでダブルクォートまたはシングルクォートで括る)
echo "myvar: $myvar"   #定義した変数を出力(myvar: 変数の値)
echo "PATH: $PATH"     #環境変数(PATH)の出力(PATH: 環境変数 PATH の値)
echo "LANG: $LANG"     #環境変数(LANG)の出力 (LANG: 環境変数 LANG の値)

この例の場合、上記スクリプトは以下のように記述しても同じ結果になります。

空白を含む文字列を変数に代入するには引用符で囲む必要がありますが、echo コマンドの引数には複数の引数を(空白区切りで)指定できるので引用符で括らなくても問題ありません。

但し、引用符で括る場合はダブルクォートを使う必要があります(シングルクォートで括るとその中の変数は展開されません)。

#! /bin/bash
myvar='my variable'    #空白を含む文字列を変数に代入するには引用符で囲む
echo myvar: $myvar    #echo コマンドには複数の引数を指定できる
echo PATH: $PATH
echo LANG: $LANG 

作成したシェルスクリプト(myscript )に実行権を付与して、パス付きで実行すると以下のように変数の値が表示されます。

chmod u+x ./myscript  return # 実行権を付与
          
./myscript   return #パス付きで実行

#以下のように変数の値が表示される
myvar: my variable  #定義した変数
PATH: /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/foo/bin  #環境変数(PATH)
LANG: ja_JP.UTF-8  #環境変数(LANG)

スクリプトの外での変数の使用

シェルスクリプトが実行される際は新たに別のシェルが起動してそこで実行され、シェルスクリプトが終了するとそれを起動した元のシェルに戻ります。

そのため、シェルスクリプトの中で定義した変数は、シェルスクリプトの中だけで使用できます。

言い換えると、シェルスクリプトの中で定義したり変更した変数は、シェルスクリプトが終了すると(スクリプトの外では)使用できませんし、変更は反映されません。

シェルスクリプトの実行後もスクリプトの中で定義した変数を参照するには、source コマンドを使って、シェルスクリプトを現在動作しているシェル(bash)に読み込ませる必要があります。

bash コマンド(bash ファイル名)の場合は、現在のシェルとは別のシェルが起動してファイルを実行しますが、source コマンドはファイルを現在のシェルで実行します。

例えば、以下のような変数を定義しただけのシェルスクリプト(myscript2)をカレントディレクトリに作成して、実行権を付与しておきます。

myscript2
#!/bin/bash
foo="Foo!"
echo $foo 

以下のようにシェルスクリプト(myscript2)を実行して、スクリプト内で定義した変数(foo)を参照しようとしても何も表示されません。

./myscript2   /return #myscript2 をパス付きで実行
Foo!    #echo $foo により変数の値が表示される
echo $foo  return #スクリプトの外で変数を確認
#何も表示されない

以下のように source コマンドでスクリプトを実行すると、スクリプト内で定義した変数(foo)を参照することができます。

source myscript2 #myscript2 を source コマンドで実行
Foo!    #echo $foo により変数の値が表示される

echo $foo  return #スクリプトの外で変数を確認
Foo!   #現在動作しているシェルに読み込まれているので、変数を参照できる

以下はコマンドラインでシェル変数を定義して、その後 myscript2 を実行する例です。

source コマンドでスクリプトを実行すると、スクリプト内で定義した変数によりコマンドラインで定義したシェル変数が上書きされます。

別の言い方をすると、source コマンドで実行した場合は変数の設定が現在のシェルに反映されます。

foo="bar" return #コマンドラインでシェル変数を定義
echo $foo return #シェル変数 $foo の値を確認
bar

$ ./myscript2 return #シェルスクリプト(bash コマンド)を実行
Foo! #シェルスクリプトで定義した $foo が表示される

$ echo $foo return #シェル変数 $foo の値を確認
bar #コマンドラインで定義した値が表示される

$ source myscript2 return #myscript2 を source コマンドで実行
Foo! #シェルスクリプトで定義した $foo が表示される

echo $foo return  #シェル変数 $foo の値を確認
Foo! #シェルスクリプトで定義した $foo によりシェル変数が上書きされている
パラメータ展開

シェルの拡張機能の変数のパラメータ展開(Parameter Expansion)を使うと、変数の値(格納されている文字列)の一部分を取り出したり置換するなどの文字列の操作ができます。

変数の値を取り出すには、変数名の先頭に $ を付けて $変数名 とします。

{ } で変数名を括って ${変数名} のように記述することもでき、他の文字列や変数と明確に区別することができます(操作を指定せずに ${変数名} とした場合は、$変数名 と同じことです)。

以下はパラメータ展開を使った変数を定義して出力するだけのスクリプト(myscript3)の例です。

myscript3
#!/bin/bash
encoding=${LANG#*.}    #環境変数 LANG からエンコーディング部分を取得
echo $encoding
command_path=${PATH##*:}    #環境変数 PATH からユーザのパス部分を取得
echo $command_path
path="/Users/foo/Documents/sample.txt"
file=${path##*/}    #変数 path からファイル部分を取得
echo $file
directory=${path%/*}*/    #変数 path からディレクトリ部分を取得
echo $directory
csvfile=${file/txt/csv}    #変数 file の拡張子部分を txt から csv に置換
echo $csvfile
myVal=${val:-7}    #変数 val が定義されてなければ値を 7 に設定
echo $myVal

上記スクリプトを実行すると変数の値が展開(操作)されて以下のように出力されます。

bash myscript3  return #bash コマンドで実行
UTF-8
/Users/foo/bin
sample.txt
/Users/foo/Documents
sample.csv
7

パラメータ展開はシェルスクリプトやコマンドラインで利用することができます。

書式 操作
${変数名} 操作を指定せず ${変数名} とした場合は、$変数名 と同じ意味
${変数名:開始位置} 開始位置(先頭は0)から最後までの部分文字列を取得
${変数名:開始位置:長さ} 開始位置(先頭は0)から長さで指定した部分文字列を取得
${変数名#パターン} 前方からの最短マッチを削除
${変数名##パターン} 前方からの最長マッチを削除
${変数名%パターン} 後方からの最短マッチを削除
${変数名%%パターン} 後方からの最長マッチを削除
${変数名/パターン/置換文字列} 最初にマッチしたパターンを置換
{変数名//パターン/置換文字列} マッチした全てのパターンを置換
${#変数} 変数に設定されている値の文字数(文字列の長さ)を取得
${変数名:-文字列} 変数が設定されている場合は変数の値を返し、変数が設定されていない場合は指定した文字列を返す
${変数名:=文字列} 変数が設定されている場合は変数の値を返し、変数が設定されていない場合は変数に指定した文字列を設定(代入)してその文字列を返す
${!接頭辞*} 接頭辞で指定した文字列から始まる変数名一覧を返す
${!接頭辞@} 接頭辞で指定した文字列から始まる変数名の一覧を返す(上記と同じ)

変数や文字列を連結

「$変数$変数」のように変数を続けて記述することで、変数の内容を連結することができます。

以下の例の echo での出力は10行目以外は全て同じ結果になります。

val1="abc"   return #変数を設定
val2="123"   return #変数を設定

echo $val1$val2   return
abc123

echo "$val1$val2"   return
abc123

echo '$val1$val2'   return  #シングルクォートでは変数は展開されない
$val1$val2

echo ${val1}${val2}   return
abc123

echo "${val1}${val2}"   return
abc123

echo "${val1}""${val2}"   return
abc123

val12=${val1}${val2}   return #変数を連結して新たに変数 val12 を設定
echo $val12  return 
abc123

以下のように変数と文字列も連結することができます。

echo $val1   return
abc

echo ${val1}def   return
abcdef

val3=${val1}def${val2}   return #変数と文字列を連結して新たに変数 val3 を設定
echo $val3   return
abcdef123

部分文字列(位置の指定)

位置を指定して、変数に格納されている文字列から部分文字列を取り出すことができます。

変数に対して開始位置(と長さ)を指定します。開始位置の先頭は 0 です。

${変数名:開始位置}      開始位置から最後までの部分文字列を取り出す
${変数名:開始位置:長さ}   開始位置から長さで指定した部分文字列を取り出す

以下は環境変数 LANG の値から部分文字列を取得する例です。

echo $LANG return #環境変数 LANG の値を表示
ja_JP.UTF-8

echo ${LANG:6} return #LANG の7文字目(6)から最後まで(先頭は 0)
UTF-8

echo ${LANG:0:5} return #LANG の1文字目(0)から5文字分(先頭は 0)
ja_JP

削除するパターンを指定(前方一致)

変数に格納されている文字列を先頭から検索して、指定したパターンと最短(または最長)でマッチした文字列までを削除することができます。

パターンには文字列だけではなく「*」や「?」などのシェルのワイルドカードが使えます。

最短マッチの場合は「#」、最長マッチの場合は「##」と指定します。

${変数名#パターン}     前方からの最短マッチを削除
${変数名##パターン}    前方からの最長マッチを削除

以下は環境変数 PATH の値から指定したパターンまでの文字列を削除する例です。

echo $PATH return #環境変数 PATH の値を表示
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/foo/bin

echo ${PATH#:} return #「:」だけを指定してもこの場合はパターンとしてマッチしない
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/foo/bin

echo ${PATH#*:} return #「*:」を指定して最短のマッチ「/usr/local/bin:」が削除される
/usr/bin:/bin:/usr/sbin:/sbin:/Users/foo/bin

echo ${PATH##*:} return #「*:」を指定して最長のマッチ「/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:」が削除される
/Users/foo/bin

以下は独自に設定した変数 path を使った例です。最後の最長マッチの削除は basename コマンドを使っても同じ結果になります。

path="/Users/foo/Documents/sample.txt" return #変数を設定
echo $path return #変数を表示
/Users/foo/Documents/sample.txt

echo ${path#*/} return #「*/」を指定して最短のマッチである先頭の「/」が削除される
Users/foo/Documents/sample.txt

echo ${path#/} return #「/」を指定(上記と同じ結果)
Users/foo/Documents/sample.txt

echo ${path##*/} return #「*/」を指定して最長のマッチ「Users/foo/Documents/」が削除される
sample.txt

basename $path return #basename コマンドを実行(上記と同じ結果)
sample.txt

シェルスクリプトでは、実行するスクリプト名が $0 という名前の変数(位置パラメータ)に格納されます。(関連:引数

スクリプト名にはパスも含まれるため、ファイル名のみを取得するには、${0##*/} と記述することができます(basename コマンド `basename $0` でも取得できます)。

以下は sciptName というスクリプトを作成して確認する例です。

sciptName
#! /bin/bash
echo "\$0: $0"
script_name=${0##*/}   # ${変数名##パターン}でファイル名のみを取得
echo "Script Name \${0##*/}: $script_name "
basename=`basename $0`
echo "Basename (basename \$0): $basename"

sciptName に実行権を付与して、実行すると以下のようになります。

./sciptName  return #スクリプトを実行
$0: ./sciptName   #実行するスクリプト名
Script Name ${0##*/}: sciptName    #ファイル名のみ表示
Basename (basename $0): sciptName    #ファイル名のみ表示

削除するパターンを指定(後方一致)

変数に格納されている文字列を後方から検索して、指定したパターンと最短(または最長)でマッチした文字列までを削除することができます。

最短マッチの場合は「%」、最長マッチの場合は「%%」と指定します。

${変数名%パターン}     後方からの最短マッチを削除
${変数名%%パターン}    後方からの最長マッチを削除

path="/Users/foo/Documents/sample.txt" return #変数を設定
echo $path return #定義した変数を表示
/Users/foo/Documents/sample.txt

echo ${path%*/} return/ #「*/」を指定(マッチしない)
/Users/foo/Documents/sample.txt

echo ${path%/*} return #「/*」を指定して最短のマッチである「/sample.txt」が削除される*/
/Users/foo/Documents

dirname $path return #dirname コマンドを実行(上記と同じ結果)
/Users/foo/Documents

echo ${path%%/*} return #「/*」を指定して最長のマッチである全てが削除される*/

echo ${path%%foo/*} return #「foo/*」を指定して最長のマッチである「foo/Documents/sample.txt」が削除される*/
/Users/

文字列置換

検索パターンと置換後の文字列を指定して変数に格納されている文字列の置換を行うことができます。

見つかったパターンのうち最初のものだけ置換する場合は「/」を指定し、見つかったパターン全てを置換する場合は「//」を指定します。

${変数名/パターン/置換文字列}    最初にマッチしたパターンを置換文字列で置換
${変数名//パターン/置換文字列}   マッチした全てのパターンを置換文字列で置換

val="jazz-pop-rock-blues" return #変数を設定
echo $val return #定義した変数を表示
jazz-pop-rock-blues

echo ${val/pop/classic} return #pop を classic に置換
jazz-classic-rock-blues

echo ${val/*-*/foo-bar} return #*-* を foo-bar に置換(*-* は jazz-pop-rock-blues にマッチ)
foo-bar

echo ${val/*-/foo-} return #*- を foo- に置換(*- は jazz-pop-rock- にマッチ)*/
foo-blues

echo ${val//-/=} return #- を全て = に置換
jazz=pop=rock=blues

変数の文字数の取得

変数に設定されている値の文字数(文字列の長さ)は、${#変数}で取得できます。

${#変数}   変数の文字数(文字列の長さ)を取得

val="abc 123" return #変数を設定
echo ${#val} return #変数の長さ(文字数)を表示
7 #空白を含めて7文字

デフォルト値の設定

変数が設定されていない場合は指定した文字列(デフォルト値)を返したり、デフォルト値を代入することができます。

${変数名:-文字列}     変数がセットされていない場合は値を返す
${変数名:=文字列}    変数がセットされていない場合は値を代入する

valx="XXXX"   return #変数 valx を設定
echo $valx   return #変数 valx を出力
XXXX

#値が設定されている場合はその値が返される
echo ${valx:-Default Value}  return 
XXXX
echo ${valx:=Default Value}  return 
XXXX

unset valx    return  #変数 valx を削除
echo ${valx:-Default Value}  return #指定した値が返される
Default Value

echo $valx  return  #${valx:- }の場合は代入はされないので何も表示されない

echo ${valx:=Default Value}  return  #指定した値が代入されて返される
Default Value

echo $valx  return   #指定した値が代入されている
Default Value

以下は、変数 myvar に変数 val7 の値が設定されていればその値を、空の場合は 777 を代入する例です。

myvar=${val7:-777}  return #変数 myvar を設定
          
echo $myvar  return #val7 が空の場合は myvar に 777 が代入される
777

変数名の一覧

接頭辞で指定した文字列から始まる変数名一覧を取得することができます。

${!接頭辞*}  または ${!接頭辞@}

以下は SHELL_ や から始まる変数名の一覧を表示する例です。

echo ${!SHELL_*}  return  #SHELL_ から始まる変数名の一覧
SHELL_SESSION_DID_HISTORY_CHECK SHELL_SESSION_DID_INIT SHELL_SESSION_DIR SHELL_SESSION_FILE SHELL_SESSION_HISTFILE SHELL_SESSION_HISTFILE_NEW SHELL_SESSION_HISTFILE_SHARED SHELL_SESSION_HISTORY SHELL_SESSION_TIMESTAMP_FILE

echo ${!TERM@}  return  #TERM から始まる変数名の一覧
TERM TERM_PROGRAM TERM_PROGRAM_VERSION TERM_SESSION_ID

man bash

パラメータ展開(Parameter Expansion)の使い方(英語)は man コマンドを使って man bash で調べることができます。

どのようなものがあるかは、以下のようにパイプを使って grep コマンドで「 ${ 」から始まり「 } 」で終わる行を行番号付き(-n)で表示すると調べやすいかも知れません。

以下では見やすいように tr コマンドでスペースを削除しています。

man bash | grep -n "\${.*}$" | tr -d " "
1281:${parameter}   
1303:${parameter:-word}
1307:${parameter:=word}
1312:${parameter:?word}
1318:${parameter:+word}
1321:${parameter:offset}     
1322:${parameter:offset:length}  
1341:${!prefix*}
1342:${!prefix@}
1346:${!name[@]}
1347:${!name[*]}
1354:${#parameter}
1361:${parameter#word}     
1362:${parameter##word}
1375:${parameter%word}
1376:${parameter%%word}
1389:${parameter/pattern/string}

配列

配列を生成するにはいくつかの方法がありますが、配列は代入によって自動的に生成されます。

個別に値を代入

以下のように各要素に個別に値を代入して配列を生成(定義)することができます。

この場合、インデックス(添字)を指定する必要がありインデックス(添字)は0から始まります。

配列名[インデックス]=値

変数名(配列名)と [ の間や = の前後には空白を入れることはできません。

また、生成した配列の要素は ${配列名[添字]} で参照することができます。

fruits[0]="Apple"  return  #1番目の要素に値を設定
fruits[1]="Melon"  return  #2番目の要素に値を設定
fruits[2]="Papaya"  return  #3番目の要素に値を設定

echo ${fruits[0]}  return  #1番目の要素の値を参照
Apple
echo ${fruits[1]}  return  #2番目の要素の値を参照
Melon
echo ${fruits[2]}  return  #3番目の要素の値を参照
Papaya   
echo "I love ${fruits[0]} ?"  return
I love Apple ?

値をまとめて代入

以下のように括弧 ( ) の中に値のリストを指定してまとめて要素を生成することができます。

配列名=(値1 値2 値3)

変数名(配列名)と ( の間や = の前後には空白を入れることはできません。

各要素はスペースで区切ります。

括弧を使用して配列を設定した場合、各要素には宣言(記述)した順に配列のインデックス 0、1、2、... n が振り当てられます。

veges=("Onion" "Potato" "Tomato")  return  #3つの要素をまとめて設定
            
echo ${veges[0]}  return  #1番目の要素の値を参照
Onion
echo ${veges[1]}  return  #2番目の要素の値を参照
Potato
echo ${veges[2]}  return  #3番目の要素の値を参照
Tomato
echo "I love ${veges[1]} ?"  return  
I love Potato ?

値の指定は文字列や数値を直接指定する以外にも、変数の値やコマンド置換を使用してコマンドの実行結果を指定することもできます。

以下は値に変数を指定して配列を生成する例です。

val="1 7 15 23"  return  #変数を設定
            
values=($val)  return  #変数を指定して配列を生成

echo ${values[0]}  return  #1番目の要素の値を参照
1
echo ${values[2]}  return  #3番目の要素の値を参照
15

以下はコマンド置換 ` `を使って ls /Users/ の実行結果を配列として生成する例です。

users=(`ls /Users/`)  return  # ls /Users/ の実行結果を配列として生成
            
echo ${users[0]}  return  #1番目の要素の値を参照
Shared
echo ${users[2]}  return  #3番目の要素の値を参照
foo

ls /Users/  return  # ls /Users/ の実行結果の例
Shared	bar	foo	boo

インデックスを指定してまとめて代入

括弧 ( ) の中でインデックスを指定してまとめて要素を生成することができます。

配列名=([インデックス]=値1 [インデックス]=値2 [インデックス]=値3)

インデックスは連番である必要はありません。

flowers=([3]="Rose" [5]="Tulip" [20]="Violet")  return  #3つの要素をインデックスを指定してまとめて設定
echo ${flowers[3]}  return  #インデックス 3 の要素の値を参照
Rose
echo ${flowers[5]}  return  #ンデックス 5 の要素の値を参照
Tulip
echo ${flowers[20]}  return  #ンデックス 20 の要素の値を参照
Violet
echo "I love ${flowers[3]} ?"
I love Rose ?

括弧 ( ) の中でインデックスを指定した要素と値だけの要素を混在させることもできます。

インデックスを指定した要素の次に、インデックスを指定していない要素を入れるとその前の要素のインデックスの値+1のインデックスが割り当てられています。

trees=("Maple" [5]="Cedar" "Pine")  return  #3つの要素をまとめて設定
echo ${!trees[@]}  return  #要素のインデックスを確認
0 5 6

echo ${trees[0]}  return  #1番目の要素の値を参照
Maple
echo ${trees[5]}  return  #ンデックス 5 の要素の値を参照
Cedar
echo ${trees[6]}  return  #ンデックス 6 の要素の値を参照
Pine

declare -a

以下のように declare -a を使って配列を生成することもできます。

declare -a 配列名=(値1 値2 値3)

declare -a pets=("Cat" "Dog" "Fish")   return  #3つの要素をまとめて設定
            
echo ${pets[0]}  return  #1番目の要素の値を参照
Cat
echo ${pets[1]}  return  #2番目の要素の値を参照
Dog
echo ${pets[2]}  return  #3番目の要素の値を参照
Fish
echo "I love ${pets[2]} ?"  return
I love Fish ?

全ての値を出力 / ${配列名[@]}

${配列名[@]} で全ての値を出力することができます。以下のように記述すると全ての値をまとめて表示することがきます。

echo ${fruits[@]}  return  #配列 fruits の全ての要素を表示
Apple Melon Papaya

echo ${veges[@]}  return  #配列 veges の全ての要素を表示
Onion Potato Tomato

echo ${flowers[@]}  return  #配列 flowers の全ての要素を表示
Rose Tulip Violet

配列の複製(再設定)

全ての値を出力する ${配列名[@]} を使って配列を複製(のようなことを)することができます。

以下は配列 fruits の全要素を新しい配列 copy_fruits の要素として設定しています。この例の場合は、元の配列 fruits の要素のインデックスが0から始まる連番になってるので複製と同じ結果になります。

echo ${fruits[@]}   return  #配列 fruits の全要素を出力
Apple Melon Papaya

copy_fruits=(${fruits[@]})   return  #新しい配列 copy_fruits に配列 fruits の全要素を設定

echo ${copy_fruits[@]}   return  #配列 copy_fruits の全要素を出力
Apple Melon Papaya

echo ${copy_fruits[0]}
Apple
echo ${copy_fruits[1]}
Melon
echo ${copy_fruits[2]}
Papaya

以下はインデックスが不連続な配列 arrayX を前述と同じ方法で複製(のようなことを)する例です。

arrayY には同じ値の要素が作成されますが、インデックスは振り直され0からの連番になります。

arrayX=([3]="NY" [7]="LA" [8]="SF")   return  #配列 arrayX を設定
          
echo ${arrayX[3]}   return  #インデックス3の要素を出力
NY
echo ${arrayX[7]}   return  #インデックス7の要素を出力
LA
echo ${arrayX[8]}   return  #インデックス8の要素を出力
SF

arrayY=(${arrayX[@]} )  return  #新しい配列 arrayY に配列 arrayX の全要素を設定

echo ${arrayY[0]}   return  #インデックス0の要素を出力
NY
echo ${arrayY[1]}   return  #インデックス1の要素を出力
LA
echo ${arrayY[2]}   return  #インデックス2の要素を出力
SF
echo ${arrayY[3]}   return  #インデックス3の要素を出力 
# 存在しない

要素の数 / {#変数名[@]}

${#変数名[@]} で配列の要素数(配列に要素が幾つあるか)を調べることができます。

echo ${#fruits[@]}  return  #配列 fruits の要素数を表示
3

echo ${#veges[@]}  return  #配列 veges の要素数を表示
3

echo ${#flowers[@]}  return  #配列 flowers の要素数を表示
3

要素の格納場所 / ${!変数名[@]}

${!変数名[@]} で配列の要素の格納場所(各要素のインデックス)を調べることができます。

echo ${!fruits[@]}   return  #配列 fruits の各要素のインデックス
0 1 2

echo ${!veges[@]}   return  #配列 veges の各要素のインデックス
0 1 2

echo ${!flowers[@]}   return  #配列 flowers の各要素のインデックス
3 5 20

echo ${!trees[@]}   return  #配列 trees の各要素のインデックス
0 5 6

空の配列

以下のように括弧 ( ) の中に何も指定せずに記述すると空の配列を生成することができます。

empty=()    return  #空の配列 empty を生成
echo ${empty[0]}   return  #1番目の要素を表示させても空なので何も表示されない

echo ${empty[@]}   return  #全ての要素を表示させても空なので何も表示されない

echo ${#empty[@]}   return  #要素数を表示 → 0
0
echo ${!empty[@]}   return  #各要素のインデックスを表示させても空なので何も表示されない

以下は空の文字列を要素に持つ配列 not_empty の例です(空の配列ではありません)。

not_empty=("")    return  #空の文字列を要素に持つ配列 not_empty を生成
echo ${not_empty[0]}   return  #1番目の要素を表示(空文字列が表示されている)

echo ${not_empty[@]}   return  #全ての要素を表示(空文字列が表示されている)

echo ${#not_empty[@]}   return  #要素数を表示 → 1
1
$ echo ${!not_empty[@]}   return  #各要素のインデックスを表示 → [0]
0

パラメータ展開

配列も変数なので、パラメータ展開が使えます。

以下は ${#変数} で設定されている文字数を、${変数名:開始位置} や ${変数名:開始位置:長さ} で設定されいる値の部分文字列を取得する例です。

sweets=("Cake" "Apple Pie" "Chocolate" "Ice Cream")    return  #配列を生成
            
echo ${#sweets[0]}    return  #1番目の要素(Cake)の文字数を表示
4

echo ${#sweets[1]}    return  #2番目の要素(Apple Pie)の文字数を表示
9

echo ${sweets[2]:3}    return  #3番目の要素(Chocolate)の4文字目からの部分文字列を表示
colate

echo ${sweets[3]:0:3}   return  #4番目の要素(Ice Cream)の0文字目から3文字を表示
Ice

値を変更

設定されている要素の値を変更するには、定義したときと同様 配列名[インデックス]=値 で新しい値に変更することができます。

配列名[インデックス]=値

echo ${sweets[0]}    return  #sweets[0] の値を表示
Cake

sweets[0]="Milk Shake"    return  #sweets[0] の値を変更

echo ${sweets[0]}    return  #sweets[0] の値を表示
Milk Shake
要素の追加

配列に値(要素)を追加するには、定義したときと同様 配列名[インデックス]=値 で新たなインデックスを指定して値を設定します。

または、以下の書式で要素を追加することができます。こちらの場合は、インデックスは自動で次の値が振り当てられるます。

配列名+=(値)

echo ${fruits[@]}    return  #既存の配列 fruits の全要素を出力(確認)
Apple Melon Papaya

fruits[3]="Banana"    return  #要素を追加: 配列名[インデックス]=値

echo ${fruits[@]}    return  #配列 fruits の全要素を出力(追加を確認)
Apple Melon Papaya Banana

echo ${fruits[3]}
Banana

fruits+=("Orange")   return  #要素を追加: 配列名+=(値)

echo ${fruits[@]}    return  #配列 fruits の全要素を出力(追加を確認)
Apple Melon Papaya Banana Orange

echo ${fruits[4]}    return  #自動でインデックス4が振り当てられる
Orange

複数の要素を追加

複数の要素をまとめて追加するには、追加する要素をスペース区切りで並べて指定します。

配列名+=(値1 値2 値3)

numbers=(111 222 333)    return  #配列 numbers を定義(生成)
echo ${numbers[@]}    return  #配列 numbers の全要素を出力
111 222 333

numbers+=(444 555 666)    return  #配列 numbers に要素を3つ追加

echo ${#numbers[@]}    return  #配列 numbers の要素数を確認
6

echo ${numbers[@]}    return  #配列 numbers の全要素を出力
111 222 333 444 555 666

変数の値を要素として追加することもできます。

echo ${numbers[@]}   return  #配列 numbers の全要素を出力
111 222 333 444 555 666

new_num="777"    return  #変数 new_num を定義

numbers+=($new_num)    return  #変数の値を要素として追加

echo ${numbers[@]}   return  #配列 numbers の全要素を出力
111 222 333 444 555 666 777

echo ${#numbers[@]}   return  #配列 numbers の要素数を確認
7

変数の値がスペース区切りの文字列の場合は複数の要素として設定されます。一つの要素として配列に追加するには、変数をダブルクォート("") で囲む必要があります。

echo ${numbers[@]}   return  #配列 numbers の全要素を出力
111 222 333 444 555 666 777
echo ${#numbers[@]}   return  #配列 numbers の要素数を確認
7

new_nums="888 999"    return  #変数 new_num(スペース区切りの文字列)を定義

numbers+=($new_nums)    return  #変数の値を要素として追加(ダブルクォートなし)

echo ${#numbers[@]}   return  #配列 numbers の要素数を確認
9  #2つの要素が追加されている

echo ${numbers[@]}   return  #配列 numbers の全要素を出力
111 222 333 444 555 666 777 888 999

str_num="000 123"   return  #変数 new_num(スペース区切りの文字列)を定義

numbers+=("$str_num")    return  #変数の値を要素として追加(ダブルクォートあり)

echo ${#numbers[@]}   return  #配列 numbers の要素数を確認
10  #1つの要素として追加されている
配列の削除

配列の削除には、変数の削除と同じ unset コマンドを使います。

配列そのものを削除するには、unset 配列名 とし、配列の要素を削除するにはインデックスを指定して unset 配列[インデックス] とします。

unset 配列名   (配列全体を削除)

unset 配列[インデックス]   (配列の要素を削除)

配列の要素を削除した場合、配列の要素数は1つ減りますが、それぞれのインデックスは変わりません。

echo ${fruits[@]}   return  #配列 fruits の全要素を出力
Apple Melon Papaya Banana Orange

echo ${!fruits[@]}   return  #配列 fruits のインデックスを確認
0 1 2 3 4

unset fruits[3]   return  #配列 fruits のインデックス3の要素を削除

echo ${fruits[@]}  return  #配列 fruits の全要素を出力
Apple Melon Papaya Orange  #インデックス3の Banana が削除されている

echo ${!fruits[@]}  return  #配列 fruits のインデックスを確認
0 1 2 4   #インデックス3が削除されている

unset fruits  return  #配列 fruits を削除

echo ${fruits[@]}   return  #配列 fruits の全要素を出力
# 削除されているので何も表示されない

要素のインデックスに [@] を指定して全ての要素を削除する(配列を空にする)こともできます。

echo ${stones[@]}   return  #配列 stones の全要素を出力
Ruby Gold Diamond Saphire Topaz

unset stones[@]   return  #全ての要素を削除

echo ${stones[@]}   return  #配列 stones の全要素を出力
# 空なので何も表示されない

echo ${#stones[@]}  return  #配列 stones の要素数を確認
0   

引数

コマンドラインで引数を指定して、シェルスクリプトに渡すことができます。

引数は指定した順番に $1、 $2、...、$n($数値)という名前の変数(位置パラメータ)に格納されます。他の変数同様、${1} のように書くこともできます。

また、$0 にはコマンド名(実行するスクリプト名)、$@ には全ての引数のリスト、$# には引数の数が格納されます。

変数 値(内容)
$0 コマンド名(実行するスクリプト名)
$1 1番目の引数
$2 2番目の引数
$n n番目の引数
$@ 全ての引数のリスト(配列)
$# 引数の個数
$? 最後に実行したコマンドの終了ステータス(結果)。成功時は 0 、失敗時は 1 や 0 以外。

以下は引数を確認するスクリプトの例(argsTest)です。

argsTest
#!/bin/bash
echo '$0: ' $0     #実行するスクリプト名
echo '$1: ' $1     #1番目の引数
echo '$2: ' $2     #2番目の引数
echo '$3: ' $3     #3番目の引数
echo '$@: ' $@     #全ての引数のリスト
echo '$#: ' $\#     #引数の個数 

スクリプトに実行権を付与して、引数(foo bar boo)を指定して実行すると以下のようになります。

chmod u+x argsTest   return  #実行権を付与
            
./argsTest foo bar boo   return  #引数(foo bar boo)を指定してスクリプトを実行
$0:  ./argsTest    #実行するスクリプト名  
$1:  foo           #1番目の引数
$2:  bar           #2番目の引数
$3:  boo           #3番目の引数
$@:  foo bar boo   #全ての引数のリスト  
$\#:  3             #引数の個数

実行するスクリプト名($0)にはパスが付いていますが、ファイル名のみを取得するにはパラメータ展開を使って ${0##*/} と記述することができます。

条件判定 test

test は条件を判定するコマンドで、条件(式)を評価して真の場合は 0 (true) を、偽 の場合は 1 (false) 終了ステータスとして返すコマンドで、画面上への出力はありません。

UNIX では終了ステータスが正常終了の場合は 0、異常終了の場合は 1 を返すため、他のプログラミング言語などの真と偽の判定と 0 と 1 が逆ですが、この場合 0 は true、1 は false になります。

test コマンドは文字列や数値の比較をしたり、ファイルの有無や属性などをチェックすることができ、コマンドラインやシェルスクリプトで使うことができます。

以下が書式です。

test 条件式

test コマンドと同等の [ コマンドを使って以下のように記述することもできます。

シェルスクリプトの if 文などの制御構文では、以下の書式で記述するのが一般的です。コマンドと引数を区切る必要があるため、条件式の前後と [ ] の間にはスペースが必要です。

[ 条件式 ]

test コマンド及び [ コマンド は内部コマンドです。以下は type コマンドで調べる例です。

type test  return
test is a shell builtin

type [  return
[ is a shell builtin

type [[  return
[[ is a shell keyword

# 外部コマンドも用意されています。
type -a test   return
test is a shell builtin
test is /bin/test

type -a [   return
[ is a shell builtin
[ is /bin/[

test コマンドの判定結果を出力で確認するには、実行した test コマンドの終了ステータス($?)を echo コマンドで出力します。

コマンドの終了ステータスは $? という特別な変数に格納されています。

以下は test コマンドの終了ステータス($?)を echo コマンドで確認する例です。

「-eq」は数値が等しいかどうかを判定する場合に使用する演算子です。

test コマンドと echo コマンドを続けて記述していますが、test コマンドは「;」で終了しています。

通常、コマンドは1行につき1つ記述しますが、コマンドを1行に複数個記述して実行する場合は「;」で区切って記述します。

test 1 -eq 1; echo $?   return  # 1と1が等しいかを判定し終了ステータスを表示
0     # true(真)

[ 3 -eq 7 ]; echo $?   return   # 3と7が等しいかを判定し終了ステータスを表示 
1    # false(偽)

[3 -eq 3 ]; echo $?   return #[ と 3 の間にスペースがない
-bash: [3: command not found    #エラー
127  # 終了ステータス(0 以外のエラーコード)

# 変数 $? には最後に実行したコマンドの終了ステータスが格納されています。

以下は演算子「-d」を使って、ファイルかどうかを判定する例です。

以下の例では test コマンドと echo コマンドを別々に実行しています。

test -f /bin/bash  return  #/bin/bash がファイルかどうかを判定
echo $?  return  #上記 test コマンドの終了ステータスを表示
0    # true(真)→ファイルである

test -f /bin  return  #/bin がファイルかどうかを判定
echo $?  return  #上記 test コマンドの終了ステータスを表示
1    # false(偽)→ファイルではない
echo $? return  #この場合は上記 echo $? コマンドの終了ステータス
0   # true(真)→echo $? は問題なく実行された

変数はダブルクォートで囲んでおくと安全

例えば、文字列が等しくない場合は true になる演算子 != を使って判定をする際に、変数を使い、その変数が未定義であったり空であった場合エラーになります。

foo="foo"    return  #変数 foo を設定
test $foo != $bar; echo $?    return  #変数 foo と bar を比較
-bash: test: foo: unary operator expected   # bar が未定義なのでエラー
2

bar="bar"    return  #変数 bar を設定
test $foo != $bar; echo $?     return  #変数 foo と bar を比較
0  #問題なく判定される(等しくないので 0 つまり true )

test $x != $foo; echo $?    return  #未定義の変数 x と foo を比較
-bash: test: !=: unary operator expected   #変数 x が未定義なのでエラー
2

test "$x" != $foo   return  #変数 x をダブルクォートで囲むとエラーにならない
echo $?
0

x=""   return  #変数 bx に "" を設定(空)
test $x == ""; echo $?  #変数 foo と空文字列を比較
-bash: test: ==: unary operator expected  #変数 x が空なのでエラー
2
test "$x" == ""; echo $?   return  #変数 x をダブルクォートで囲むとエラーにならない
0

関連項目:変数と引用符

演算子

test コマンドでは、様々な演算子を使って数値や文字列、ファイルなどを判定することができます。

ファイル属性

以下はファイルの種類や属性を判定する際に使用できる演算子です。

ファイルの判定に使用する演算子
演算子 真になる条件
-d ファイル名 ディレクトリの場合
-e ファイル名 ファイルが存在する場合(種類に関係なく)
-f ファイル名 ファイルの場合
-L ファイル名 ファイルがシンボリックリンクの場合
-s ファイル名 ファイルのサイズが0ではない(ファイルが空ではない)場合
-r ファイル名 読み取り可能な場合
-w ファイル名 書き込み可能な場合
-x ファイル名 実行可能な場合
ファイル1 -nt ファイル2 ファイル1の更新時刻がファイル2のより新しい場合(newer than)
ファイル1 -ot ファイル2 ファイル1の更新時刻がファイル2のより古い場合(older than)
ファイル1 -ef ファイル2 ファイル1とファイル2の実体が同じ(シンボリックリンク先が同じまたはハードリンク先が同じ)場合
touch first.txt  return  #空のファイルを作成
touch second.txt  return  #空のファイルを作成

test -s first.txt; echo $?  return  #ファイルが空でないかを確認
1  #false ファイルは空

[ -w first.txt ]; echo $?  return  #ファイルが書き込み可能どうかを確認
0  #true 書き込み可能

test first.txt -nt second.txt; echo $?  return  #first.txt は second.txt より新しいか?
1  #false 新しくない

test first.txt -ot second.txt; echo $?  return  #first.txt は second.txt より古いか?
0  #true 古い

文字列

以下は文字列を比較したり判定する際に使用できる演算子です。

※演算子と文字列の間にはスペースが必要です。

文字列の比較(判定)に使用する演算子
演算子 真になる条件
文字列 文字列が null でない(定義されている)場合
-n 文字列 文字列の長さが 0 でない場合
-z 文字列 文字列の長さが 0 の場合
文字列1 = 文字列2 文字列1と文字列2が等しい場合
文字列1 != 文字列2 文字列1と文字列2が等しくない場合
文字列 =~ パターン(※) 文字列が指定したパターンにマッチする場合
文字列1 < 文字列2(※※) ASCIIコード順に比較して文字列1が文字列2より前になる場合
文字列1 > 文字列2(※※) ASCIIコード順に比較して文字列1が文字列2より後になる場合

以下は変数 $val1 に文字列「abc」を設定した場合の例です。変数 $val2 は定義されていません。

val1="abc"  return  #変数 val1 に文字 abc を代入
test $val1; echo $?  return  #文字列 $val1 を評価
0  #true
test $val2; echo $?  return  #文字列 $val2(定義されていない)を評価
1  #false

test -n $val1; echo $? return  #文字列 $val1 の長さが 0 でないか?
0  #true(0でない)
test -z $val1; echo $? return  #文字列 $val1 の長さが 0 か?
1 #false(0でない)

#変数が定義されていないと、-n や -z は正常に動作しないようです。
#以下は定義されていない変数 $val2 の例ですが、正しく判定されていません。
test -n $val2; echo $? return 
0  #true(0でない)

test -z $val2; echo $? return 
0 #true(0である)

$ val2="123"  return  #変数 val2 に文字 123 を代入

test $val1 = $val2; echo $?  return  #$val1 と $val2 が等しいか?
1 #false(等しくない)

test $val1 != $val2; echo $?  return  #$val1 と $val2 が等しくないか?
0 #true(等しくない)

test $val1=$val2; echo $? return  #$val1 と $val2 が等しいか?(= の前後にスペースがない)
0 # 演算子 = の前後にスペースがないため正しく評価されない(23行目の結果と異なる)

(※)「=~」の演算子は [[ の書式(複合コマンド)でのみ使用可能です。

以下はパターンマッチを使って特定の文字列が含まれるかを判定する例です。

echo $SHELL   return  #変数の値を確認
/bin/bash

[[ $SHELL =~ bash ]]; echo $?   return  #bash が含まれるか?
0  #true(含まれる)

echo $LANG   return  #変数の値を確認
ja_JP.UTF-8

 [[ $LANG =~ UTF ]]; echo $?   return  #UTF が含まれるか?
0  #true(含まれる)

[[ $LANG =~ .*_JP.* ]]; echo $?   return  #_JP が含まれるか?
0  #true(含まれる)

[[ $LANG =~ JA ]]; echo $?   return  #JA が含まれるか?
11 #false (含まれない)

[ $LANG =~ JA ]; echo $?  return  #使用できない
-bash: [: =~: binary operator expected  #エラー
2

test $LANG =~ JA ; echo $?  return  #使用できない
-bash: test: =~: binary operator expected  #エラー
2

(※※)「<」と「>」の演算子は [[ の書式(複合コマンド)でのみ使用可能です。

char1="a"  return  #変数 char1 に文字 a を代入
char2="b"  return  #変数 char2 に文字 b を代入

[[ $char1 < $char2 ]]; echo $?  return  #a と b を比較
0    #true a は b よりも前

[[ $char2 < $char1 ]]; echo $?  return  #b と a を比較
1   #false b は a よりも前ではない

[[ xyz > xzy ]]; echo $?  return  #xyz と xzy を比較
1   #false xyz は xzy よりも後ではない

[[ xyz > xab ]]; echo $?  return  #xyz と xab を比較
0   #true xyz は xab よりも後

test $char1 < $char2; echo $?  return  #使用できない
-bash: b: No such file or directory  #エラー
1

[ $char1 < $char2 ]; echo $?  return  #使用できない
-bash: b: No such file or directory  #エラー
1

以下は数値(整数)を比較する際に使用できる演算子です。

数値(整数)の比較に使用する演算子
演算子 真になる条件
数値1 -eq 数値2 数値1と数値2が等しい場合(equal)
数値1 -ne 数値2 数値1と数値2が等しくない場合(not equal)
数値1 -gt 数値2 数値1が数値2より大きい場合(greater than)
数値1 -ge 数値2 数値1が数値2より大きいか等しい場合(greater than or equal)
数値1 -lt 数値2 数値1が数値2より小さい場合(less than)
数値1 -le 数値2 数値1が数値2より小さいか等しい場合(less than or equal)

論理演算

論理演算で評価する場合に使用する演算子
演算子 真になる条件
条件式1 -a 条件式2 条件式1と条件式2がどちらも真の場合(and)
条件式1 -o 条件式2 条件式1と条件式2のどちらかが真の場合(or)
! 条件式 条件式が偽の場合(not)
test 3 -eq 3 -a 5 -gt 3; echo $?  return
0  #true 「3は3と等しい」且つ「5は3より大きい」→ 真

test 3 -eq 3 -a 5 -lt 3; echo $?  return 
1  #false 「3は3と等しい」且つ「5は3より小さい」→ 偽

test 3 -eq 3 -o 5 -lt 3; echo $?  return 
0  #true 「3は3と等しい」または「5は3より小さい」→ 真

test 3 -ne 3 -o 5 -lt 3; echo $?  return 
1  #false 「3は3と等しくない」または「5は3より小さい」→ 偽

-a の代わりに && を使って以下のように記述することもできます。

test 3 -eq 3 && test 5 -gt 3; echo $?  return
0  #true 「3は3と等しい」且つ「5は3より大きい」→ 真

[ 3 -eq 3 ] && [ 5 -gt 3 ]; echo $?  return
0  #true 「3は3と等しい」且つ「5は3より大きい」→ 真

-o の代わりに || を使って以下のように記述することもできます。

test 3 -ne 3 || test 5 -lt  3; echo $?  return
1  #false 「3は3と等しくない」または「5は3より小さい」→ 偽

[ 3 -ne 3 ] || [ 5 -lt 3 ]; echo $?  return
1  #false 「3は3と等しくない」または「5は3より小さい」→ 偽

以下は ! を使って条件判定を反転させる例です。

test ! 3 -eq 3; echo $?  return
1  #false

test ! "abc" = "abc"; echo $?  return
1  #false
            
[ ! "abc" = "abc" ]; echo $?
1  #false 

キーボードからの入力を受け取る read

read コマンドはユーザからの入力(標準入力)を1行読み取り,変数に代入します。

read コマンドは以下のように内部コマンドと外部コマンドが用意されています。

type -a read  return
read is a shell builtin   # 内部コマンド
read is /usr/bin/read

内部コマンド read のマニュアルは help read で見ることができます。

help read return
read: read [-ers] [-u fd] [-t timeout] [-p prompt] [-a array] [-n nchars] [-d delim] [name ...]
    One line is read from the standard input, 
    or from file descriptor FD if the -u option is supplied, 
    and the first word is assigned to the first NAME, 
    the second word to the second NAME, and so on, 
    with leftover words assigned to the last NAME.  
    Only the characters found in $IFS are recognized as word delimiters.  
    If no NAMEs are supplied, the line read is stored in the REPLY variable.  
    If the -r option is given, this signifies 'raw' input, and
    backslash escaping is disabled.  
    The -d option causes read to continue
    until the first character of DELIM is read, rather than newline.  
    If the -p option is supplied, the string PROMPT is output 
    without a trailing newline before attempting to read.  
    If -a is supplied, the words read are assigned to 
    sequential indices of ARRAY, starting at zero.  
    If -e is supplied and the shell is interactive, 
    readline is used to obtain the line.  
    If -n is supplied with a non-zero NCHARS argument, 
    read returns after NCHARS characters have been read.  
    The -s option causes input coming from a terminal to not be echoed.
    
    The -t option causes read to time out and return failure if a complete line
    of input is not read within TIMEOUT seconds.  If the TMOUT variable is set,
    its value is the default timeout.  The return code is zero, unless end-of-file
    is encountered, read times out, or an invalid file descriptor is supplied as
    the argument to -u.

以下がおおまかな書式です。

read [オプション] [変数名 ...]

以下のようなオプションがあります。

オプション 意味
-a array(配列名) 読み込んだ単語(空白区切りの文字列)を配列に格納する
-d delim(区切り文字) 読み込みを終了する区切り文字を指定(デフォルトは改行)
-n ncharsz(文字数) 指定した文字数の入力を受け取ったら読み取りを終了
-p prompt(プロンプト文字列) 読み込み前にプロンプト文字列を表示する
-r 読み込む文字のバックスラッシュによるエスケープを無効にする(バックスラッシュを単なる文字として扱う)
-t timeout(秒数) 入力待ちの時間を秒で指定
-u fd(ファイルディスクリプタ) 標準入力ではなく指定したファイルディスクリプタから読み込む

read コマンドはユーザからの入力を1行読み取り(指定した)変数に代入します。

以下はコマンドラインで実行する例で、変数名に input を指定しています。

2行目はキーボードからの入力です。

3行目は変数 $input に代入された値(キーボードから入力した値)を出力しています。

read input  return  #入力待ちの状態になる
Here we go! return  #キーボードから「Here we go!」と入力して return を押す
echo $input  return  #変数 $input を出力
Here we go!   #変数 $input の値が表示される

変数名を指定しない場合は、ユーザからの入力をデフォルトの変数 REPLY に格納します。

read  return  #入力待ちの状態になる
OK return  #キーボードから「OK」と入力して return を押す
echo $REPLY  return  #変数 $REPLY を出力
OK

複数の文字列(単語)を受け取る

read コマンドで引数に複数の変数を指定した場合は、入力した行は区切り文字で区切った複数の文字列(単語)に区切られ、それぞれ変数に代入されます。

区切り文字の初期値は空白またはタブで、環境変数 IFS で設定(変更)できます。

以下は read コマンドの引数に3つの変数を指定して、入力されたスペース区切りの文字列を受け取り変数に代入する例です。

read str1 str2 str3    return  #引数に3つの変数を指定
abc 123 #$%  return  #空白区切りで3つの文字列を入力

echo $str1  return  #変数 $str1 に代入された値を出力
abc
echo $str2  return  #変数 $str2 に代入された値を出力
123
echo $str3  return  #変数 $str3 に代入された値を出力
#$%

指定した変数の数より多い入力がある場合は、最後の変数にまとめて代入されます。

read str1 str2 str3   return  #引数に3つの変数を指定
111 222 333 444 return  #空白区切りで4つの文字列を入力

echo $str1  return
111
echo $str2  return
222
echo $str3  return  #多い分は最後の変数に代入される
333 444

指定した変数の数より入力が少ない場合は、残りの変数は空白になります。

read  str1 str2 str3   return  #引数に3つの変数を指定
aaa   return  #1つの文字列のみを入力

echo $str1  return
aaa
echo $str2  return  #入力が少ないので空白

echo $str3  return  #入力が少ないので空白

区切り文字を設定 IFS

以下はデフォルトの区切り文字を : に変更する例です(少し面倒な方法です)。

まず、現在の環境変数 IFS の値をバックアップしてから変更し、後で元に戻します(シェルを再起動すれば元に戻ります)。

ORG_IFS=$IFS   return  #デフォルトの設定を変数 ORG_IFS にバックアップ
          
IFS=:   return  #IFS の値(区切り文字)を : に変更

read s1 s2 s3   return  #引数に3つの変数を指定
New York:Los Angels:Houston   return  # : 区切りで文字列を入力

echo $s1   return 
New York
echo $s2   return 
Los Angels
echo $s3   return 
Houston
IFS=$ORG_IFS   return  #デフォルトの設定に戻す

以下は、前述の方法のより簡単です。

read コマンドの前に IFS=x と区切り文字(x)を指定して、IFS を一時的に変更して read コマンドを実行します。

注意しなければならないのは、IFS=x の後にはセミコロンを記述しないようにします。(セミコロンを付けて実行すると、IFS の値が変更されてしまいます)

以下は前述と同じことを、もう少し簡単に行う方法です。

IFS=: read s1 s2 s3  return  #IFS の値を : に変更して read コマンドを実行
          
New York: Los Angels : Houston  return  # : 区切りで文字列を入力

echo $s1   return 
New York
echo $s2   return 
Los Angels
echo $s3   return 
Houston
echo $IFS   return   #IFS の値は変更されていない


IFS

IFS(Internal Field Separator)は bash の環境変数(組み込み変数)でデフォルトは ' \t\n'(空白、タブ、改行)で、read コマンドの展開後の単語や行の区切りなどに使われています。以下は man bash からの抜粋です。

IFS    The  Internal  Field  Separator  that is used for word splitting
after expansion and to split lines  into  words  with  the  read
builtin  command.   The  default  value  is  ``<space><tab><new-line>''.

※ IFS の値は空白文字なので echo してもわからない(分かりづらい)です。

echo "環境変数IFS ここから→${IFS}←ここまで"  return  #デフォルトの IFS を出力
環境変数IFS ここから→ 	
←ここまで

読み込みを終了する文字を変更 -d

読み込み(入力)を終了する区切り文字はデフォルトでは改行ですが、-d を指定して別の文字に変更することができます。

以下は、読み込み(入力)を終了する区切り文字を「%」に指定した例です。

以下の例では、わかりやすいようにターミナルのプロンプト「$」を表示しています。

入力を開始して、「%」を入力した時点で read コマンドの入力が終了し、「$」というターミナルのプロンプト文が表示されています。

$ read -d % str  return  #-d オプションで「%」を指定
abcde% $   return  #「%」が入力された時点で入力が終了し、ターミナルのプロンプトが表示される
$ echo $str   return  #変数 $str を出力
abcde

入力を配列に代入 -a

-a オプションを指定すると指定した変数を配列変数として扱い、入力した行を空白(区切り文字)で区切り配列変数(配列)に代入します。

区切られる文字列(単語)の数がわからない場合などで便利です。

配列変数のインデックスは0から順に代入されていきます。

read -a input_array  return  #-a オプションを指定して引数に(配列)変数を指定
Sun River Sea  return  #空白区切りで3つの文字列を入力

echo ${input_array[0]}  return  #1番目の要素を出力
Sun
echo ${input_array[1]}  return  #2番目の要素を出力
River
echo ${input_array[2]}  return  #3番目の要素を出力
Sea

echo ${input_array[@]}  return  #全ての要素をまとめて出力
Sun River Sea

echo ${#input_array[@]}  return  #要素数を出力
3

echo ${!input_array[@]}  return  #要素のインデックスを出力
0 1 2

入力文字数を制限 -n

-n オプションを使うと指定した文字数が入力された時点で読み込みが終了し次の処理に進みます。

以下は7文字入力したら、読み込みを終了する例です。

以下の例では、わかりやすいようにターミナルのプロンプト「$」を表示しています。

$ read -n 7 str return  #-n オプションで「7」を指定
ABCDEFG  $   return  #7文字が入力された時点で入力が終了し、ターミナルのプロンプトが表示される
$ echo $str   return  #変数 $str を出力
ABCDEFG

プロンプトを表示 -p

-p オプションを指定すると、入力行の先頭に表示するプロンプト(文字)を設定することができます。

入力してもらう内容などを表示でき、入力待ちの状態であることを示すことができます。

read -p "Enter 2 words: >" str1 str2  return  #-p オプションを指定
Enter 2 words: >Star Dust  return  #プロンプトが入力行の先頭に表示される

echo $str1
Star

echo $str2
Dust

入力時間を制限 -t

-t オプションを指定すると、入力できる時間を制限することができます。時間は秒数で指定します。

何も入力せずに指定した時間(秒数)が経過すると、入力待ち状態が終了しプロンプトが戻ります。

以下は入力時間を5秒と設定する例です。

read -t 5 -p "timeout in 5sec:> " str return  # -t 5(5秒と指定)
timeout in 5sec:> $   #5秒間入力がないとプロンプトが表示される

read コマンド(IFS)参考サイト:read - 標準入力から行を読み取る

連番・数列を生成 seq

seq コマンドは連続した番号や一定間隔の数字の列(sequence of numbers)を出力するコマンドです。

以下が書式です。

seq [オプション] [開始値 [増分]] 終了値

「開始値」「増分」「終了値」には整数の他に小数や負の値(マイナス)を指定することができます。

以下のようなオプションがあります。

オプション 意味
-f format(フォーマット文字列) フォーマット文字列を使って数字の書式を設定
-s string(文字列) 数字の区切り文字列を指定(デフォルトは改行)
-t string(文字列) 出力の最後に指定した文字を出力
-w 数字の幅を等しくするように先頭を0追加

seq 終了値

終了値のみ(必須の引数)を指定すると、1から指定した数値までの連続した数値を出力します。

seq 5 return  #終了値のみを指定
1
2
3
4
5

seq 開始値 終了値

開始値と終了値を指定すると、開始値から終了値までの連続した数値を出力します。

seq 5 10  return  #開始値と終了値を指定
5
6
7
8
9
10

seq 開始値 増分 終了値

増分は開始値と終了値の間に指定します。

seq 0 5 20  return  #開始値 増分 終了値を指定
0
5
10
15
20

seq 5 0.5 8.5  return  # 0.5 間隔で増加
5
5.5
6
6.5
7
7.5
8
8.5

減少(逆順で出力)

終了値を開始値より小さい値に指定すると減少させて連番を出力することができます。

seq 10 5  return  #10〜5まで連番で(減少)
10
9
8
7
6
5

seq 10 -1 5  return  #前述と同じこと
10
9
8
7
6
5

seq 10 -2 5  return  #2ずつ減少させる
10
8
6

seq 0.5 -0.25 -2  return  #0.25ずつ減少させる
0.5
0.25
0
-0.25
-0.5
-0.75
-1
-1.25
-1.5
-1.75
-2

先頭を0で埋める -w

-w オプションを指定すると、先頭(や末尾)を0で埋めて数字の幅を等しくすることができます。

seq -w 5 10  return  #-w オプションを指定して先頭を0で埋める
05
06
07
08
09
10

seq -w 0 50 1000  return  # 50間隔で1000まで
0000
0050
0100
0150
0200
・・・
0850
0900
0950
1000

seq -w 0.5 -0.25 -2  return  #0.25ずつ減少(先頭と末尾を0で埋める)
00.50
00.25
00.00
-0.25
-0.50
-0.75
-1.00
-1.25
-1.50
-1.75
-2.00

区切り文字を変更 -s

デフォルトの区切り文字は改行(\n)ですが、-s オプションを指定すると変更することができます。

seq -s , 5  return  # 区切り文字をカンマに変更
1,2,3,4,5,  #改行されないのでプロンプトがこの位置にきます

seq -s ", " 5 return  # 区切り文字を「カンマ+スペース」に変更
1, 2, 3, 4, 5,   #改行されないのでプロンプトがこの位置にきます

$ seq -s ",\n" 5  return # 区切り文字を「カンマ+改行」に変更
1,
2,
3,
4,
5,

seq -w -s ".txt\n" 10  return # 区切り文字を「.txt+改行」に変更
01.txt
02.txt
03.txt
04.txt
05.txt
06.txt
07.txt
08.txt
09.txt
10.txt
# このような場合は -f オプション使う方が自由度が高いです。

最後に指定した文字を出力 -t

-w オプションを指定すると、出力の最後に指定した文字を出力することができます。

-s オプションで改行を使わない場合に、最後に改行文字を出力する際に便利です。

seq -s , -t "\n" 5  return #区切り文字をカンマに変更し最後に改行を出力
1,2,3,4,5,

seq -t "End\n" 5 return #最後に「End+改行」を出力
1
2
3
4
5
End

書式を指定 -f

-f オプションを指定してフォーマット文字列を使って数字の書式を指定することができます。

以下のフォーマット指定子を使用できます。

フォーマット指定子 意味
%f 通常表記の浮動小数点数
%e 指数表記の浮動小数点数(小文字)
%E 指数表記の浮動小数点数(大文字)
%g 数値の値(大きさ)に応じた浮動小数点数(小文字)
%G 数値の値(大きさ)に応じた浮動小数点数(大文字)

例えば、小数点以下2桁で出力するには "%0.2f" と指定します。

また、フォーマット指定子の前後に任意の文字列を指定することもできます。引用符は省略可能です。

seq -f "%0.2f" 0.5 0.1 1  return #小数点以下2桁で出力
0.50
0.60
0.70
0.80
0.90
1.00

seq -f "%0.3fppm" 0.05 0.01 0.1  return #文字列を付加して出力
0.050ppm
0.060ppm
0.070ppm
0.080ppm
0.090ppm
0.100ppm

seq -f "%e" 1000 100 2000  return #指数表記(小文字)
1.000000e+03
1.100000e+03
1.200000e+03
1.300000e+03
・・・
1.800000e+03
1.900000e+03
2.000000e+03

seq -f "%0.1E" 1000 100 2000  return #指数表記(大文字桁数指定)
1.0E+03
1.1E+03
1.2E+03
1.3E+03
・・・
1.8E+03
1.9E+03
2.0E+03

整数で出力する際に桁数を指定するには %g を使って、例えば3桁であれば %3g、先頭を0で埋める場合は %03g のように指定します。

seq -f "%3g" 1 5  return #3桁で整数を出力
  1
  2
  3
  4
  5
  
test $ seq -f "%03g" 1 5  return #先頭を0で埋めて3桁で出力
001
002
003
004
005

test $ seq -f "sample_%03g.txt" 1 5  return #先頭を0で埋めて3桁で前後に文字列を付けて出力
sample_001.txt
sample_002.txt
sample_003.txt
sample_004.txt
sample_005.txt

四則演算(算術)

シェルで四則演算を行うにはいくつかの方法があります。

例えば、1+2 という足し算を以下のように記述しても計算することはできません。

foo=1+2   return #変数 foo に 1+2 の結果を代入したつもり
          
echo $foo   return #変数 foo を出力
1+2   #文字列として出力される

シェルで足し算をするには、以下のような方法があります。

let foo=1+2   return # let コマンド
echo $foo   return
3

((foo=1+2))   return # let コマンドの別の書き方
echo $foo   return
3

echo $((1+2))   return # 算術式展開
3

foo=$((1+2))  return # 算術式展開(変数に代入)
echo $foo
3

expr 1 + 2   return # expr コマンド(結果の出力先は標準出力)
3

foo=$(expr 1 + 2)   return # expr コマンドの結果をコマンド置換で変数に代入
echo $foo   return
3


# let コマンドや算術式展開では式に空白を含めないが、expr コマンドでは演算子の前後に空白が必要

let コマンドは、四則演算や論理演算などの算術式を評価(計算)するシェルの内部コマンドです。

let コマンドの代わりに,((算術式)) と記述することで let コマンドと同様に算術式を評価できます。

式の結果をすぐに表示(展開)するには、echo $((算術式)) のように記述でき、算術式展開(arithmetic expansion)と呼びます。

シェルスクリプトの中では、算術式を評価(計算)するには ((算術式)) と記述し、評価した値を取り出すには $ を付けて $((算術式)) とするのが一般的です。

let 変数=算術式      # let コマンド
        
((変数=算術式))      # let コマンドの別の書き方

echo $((算術式))    # 算術式展開(結果を展開)

変数=$((算術式))     # 算術式展開(結果を展開)

expr コマンドは、式を評価して標準出力に結果を書き込む外部コマンドで、整数の四則演算や正規表現を使ったパターン判定が可能です。

expr 式           # 結果は出力される→結果の出力先は標準出力
      
変数=`expr 式`     # 結果をコマンド置換で変数に代入

または 変数=$(expr 式) 

処理速度

数式の算出をするだけであれば、let コマンドの方が expr コマンドよりはるかに速いので、ループ内の繰り返しなどでは let コマンドを使ったほうが良いようです。

let コマンド (( ))

let コマンドは、四則演算や論理演算を含む算術式による計算を行って評価する bash の内部(組み込み)コマンドです。

  • 四則演算では整数だけを扱えます(小数の計算は bc コマンドを使います)
  • 算術式の中ではパス名展開をしないので * をエスケープする必要はありません
  • 算術式の中で変数を使う場合、$ は不要です
  • 算術式の中では空白を含めることができません
  • 変数が null や未設定の場合は値は0として扱われます
  • 演算を括弧でまとめると演算の優先順位を変更できます
  • ((算術式)) と記述することができます

以下が書式です。

let 変数=算術式

以下のように記述することができます。

((変数=算術式))

算術式の結果を展開するには $ を付けて以下のように記述し、算術式展開と呼ばれます。

$((算術式))    

算術式展開は、bash が算術式を解釈及び計算して計算結果に置き換えます。

以下は四則演算の使用例です。

let foo=1+2  return # let 変数=算術式      
echo $foo  return
3

((bar=3*4))  return # ((変数=算術式))
echo $bar  return
12

((foo2=(foo+bar)*6)) return # ((変数=算術式)) 括弧でまとめて優先順位を変更
echo $foo2  return
90

echo $((3>1))  return # echo $((算術式))  算術式展開
1   # 1は true

echo 5-3=$((5-3))  return # $((算術式))  算術式展開
5-3=2

x=$((12/3))    return # 変数= $((算術式))  算術式展開
echo $((x*3))  return # echo $((算術式))  算術式展開
12

#算術式の中では空白を含めることができない
#算術式の中で変数を使う場合 $ は不要
#算術式の中では * のエスケープは不要

以下のような演算子が使えます。

使用できる演算子の一部抜粋
演算子 意味
+ 加算
- 減算
* 乗算
/ 除算
% 剰余(余り)
** 累乗
変数++ 変数を評価した後で1増やす(後置インクリメント)
変数-- 変数を評価した後で1減らす(後置デクリメント)
++変数 変数を1増やしてから評価する(前置インクリメント)
--変数 変数を1減らしてから評価する(前置デクリメント)
<=、 >=、 <、 > 大小の比較(true は 1、false は 0)
== 等しい(true は 1、false は 0)
!= 等しくない(true は 1、false は 0)
= 代入演算子
+=、-=、*=、/=、%= など 複合代入演算子
! 論理否定
&& 論理AND
|| 論理OR
式1 ? 式2 : 式3 三項演算(式1が true ならば式2、式1が false なら式3を評価)

以下は演算子の使用例です。

((x=3<5))   return # 変数 x に 3 と 5 の大小を比較した結果を代入
echo $x   return # 変数 x を出力
1  # 3 は 5 より小さいので true(1)

echo $((8!=8))   return # 8と8が「等しくない」かを判定して結果を出力(算術式展開)
0  # 8 と 8 は等しいので false(0)

y=123   return # 変数 y に 123 を代入

((y+=10))   return # 変数 y の値を 10 増加

echo $y   return # 変数 y を出力
133  # 123+10 

echo $((y*=5))   return # 変数yの値(133)に 5 をかけた結果を出力(算術式展開)
665  # 133x5

w=0   return # 変数wに0を代入

((z=w?w:y))   return # w の値が true なら w を、そうでなければ y の値を z に代入(三項演算)

echo $z   return # 変数zを出力
665  # w は 0 なので false になり、y の値(665)が代入される

w=1   return # 変数 w に 1 を代入

echo $((z=w?w:y))   return # (三項演算と算術式展開)
# w の値が true なら w を、そうでなければ y の値を z に代入した結果を出力

1  # w に 1 が代入されて true になり、w の値(1)が代入される

以下はインクリメント演算子の使用例です。演算子の位置により動作が異なります。

foo=0    return # foo に0を代入
((foo++))    return #後置インクリメントで値を1増やす

echo $foo    return #foo は1に
1

echo $((foo++))    return #評価した時点では1で、その後1増加される
1

echo $foo    return #foo は2になっている
2

echo $((++foo))    return #評価する前に1増やしている
3

echo $foo    return #評価前に1増えているのでそのまま
3

以下は注意が必要なインクリメント演算子を使った例です。

以下のような誤った書き方をすると、変数 foo の値は 0 のままです。

foo=0      return # foo に 0 を代入
foo=$((foo++))      return # 評価した時点で foo は0なので foo には 0 が代入される
echo $foo
0
foo=$((foo++))      return # foo は 0 のまま
echo $foo
0

もし上記のような書き方をする場合は、前置インクリメントを使います。

foo=0      return # foo に 0 を代入
foo=$((++foo))      return # 評価した時点で foo は 1 なので foo には 1 が代入される
echo $foo
1
foo=$((++foo))      return # 評価した時点で foo は 2 なので foo には 2 が代入される
echo $foo
2
foo=$((foo+1))      return # インクリメント演算子を使わない場合
echo $foo
3

n 進数リテラル

8進数や16進数など n 進数を記述することができます。

進数 表記
10進数 1〜9 の数字で始まる数値
8進数 0 から始まる整数
16進数 0x または 0X で始まる整数
n 進数 n# で始まる整数(n は 2 以上 64 以下)

echo $((07654))    return # 8 進表記
4012

echo $((0xC14F))    return # 16 進表記
49487

echo $((2#11111110))    return # 2 進表記
254

((foo=2#11111100+2#00000011))   return # 2 進表記
echo $foo  return
255

echo $((2#11111100+2#00000011))   return # 2 進表記
255
expr コマンド

expr コマンドは、式を評価して結果を標準出力に表示(出力)します。

四則演算や比較演算子を使った判定などが可能です。

以下が書式です。

expr 式

式の要素はすべて別々の引数として指定する必要があるので、演算子の前後には空白が必要です。

また、* や (、) 、<、> のようなシェルで特別な意味を持つ記号(メタ文字)を使う場合は、バックスペースでエスケープするか引用符で括る必要があります。

四則演算

整数の四則演算が可能です。(小数の計算は bc コマンドを使います)

出力
引数1 + 引数2 引数1と引数2を足した結果(加算)
引数1 - 引数2 引数1から引数2を引いた結果(減算)
引数1 * 引数2 数1と引数2を掛けた結果(乗算)
引数1 / 引数2 引数1を引数2で割った結果の整数部分(除算)
引数1 % 引数2 引数1を引数2で割った余り(剰余算)

以下は四則演算の例です。括弧を使って優先度を変更することもできます。

expr 1 + 2   return 
3

expr 3 - 2   return 
1

expr 3 \* 4   return # * をバックスラッシュでエスケープ
12

expr 3 "*" 4   return  # * を引用符でエスケープ
12

expr 7 / 2   return 
3

expr 7 % 2   return 
1

expr 3 + 2 \* 4  return 
11

expr \( 3 + 2 \) \* 4  return   # 括弧を使って優先度を変更(括弧はエスケープします)
20

計算結果を変数に入れるにはコマンド置換 `` または $() を使います。

x=`expr 3 + 7`   return  # コマンド置換 `` 
        
echo $x   return 
10

y=$(expr $x - 8)   return  # コマンド置換 $() 

echo $y   return 
2

値の比較(判定)

両方の引数が整数の場合は,整数を比較した結果を返します。

それ以外の場合は,辞書並び順で文字列を比較した結果を返します。

結果は true の場合は 1,false の場合は 0 になります。

true(1)になる条件
= 左辺の値と右辺の値が等しい
!= 左辺の値と右辺の値が等しくない
> 左辺の値が大きい
>= 左辺の値が大きいか右辺の値と等しい
< 左辺の値が小さい
<= 左辺の値が小さいか右辺の値と等しい
expr 3 != 5  return  
1  # 等しくないので true

expr 8 \<  7  return  # < はエスケープ
0  # 8 は 7 より小さくないので false

x=10 y=20  return  # 変数を設定

expr $x \>= $y  return  # > はエスケープ
0  # $x は $y より大きくないので false 

expr 123 \< abc  return  #文字列として辞書順で比較
1  # 123 の方が辞書順で前なので true

expr abc \> xyz  return  #文字列として辞書順で比較
0  # abc の方が辞書順で前なので false
小数の計算 bc コマンド

bc コマンドは四則演算や複雑な計算をすることができ、必要に応じて小数点以下の桁数を指定することができます。

以下が書式です。

bc [オプション] [ファイル]

標準入力やファイルから計算式を入力して(読み込んで)実行することもできます。

以下はパイプを使ってコマンドラインから計算式を入力する書式です。

echo 計算式 | bc

以下のようなオプションがあります。

オプション 意味
-h ヘルプを表示
-i 対話モードを強制
-l 標準数学ライブラリを読み込んで起動
-q 起動時のメッセージを表示しない
-v バージョンを表示

bc を起動するとバージョンや著作権情報などの後に、カーソルが表示されます。

カーソルの位置に計算を入力し、return を押すと結果が表示されます。

終了するには quit とタイプして return を押します。

左右の矢印キーで移動でき delete キーで文字を削除できます。

上下の矢印キーで実行した計算式を呼び出すことができます。

小数点以下を表示するには、特別な変数 scale に scale=桁数 のように指定します(初期値は0)。

bc  return  #bc を起動
bc 1.06
Copyright 1991-1994, 1997, 1998, 2000 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'`. 

432*3.14  return  #乗算 432 x 3.14
1356.48

100/7  return  #除算(小数の桁数を指定していないので小数点以下が表示されない)
14

scale=8  return  #小数点以下の桁を変数 scale に指定(初期値は0)

100/7  return  #同じ除算を再度実行(↑キーが使える)
14.28571428

(4+3)*3/7  return  #括弧で優先度を変更可能
3

pi=3.14  return  #変数に代入
r=4      return  #変数に代入
r^2*pi   return  #円周を計算(^ はべき乗)
50.24

sqrt(2)   return  #2の平方根
1.41421356

length(12345.6789)   return  #指定した数値の有効桁数
9

scale(12345.6789)   return  #指定した数値の小数点以下の有効桁数
4

quit  return  #終了するには quit とタイプ

特別な変数

以下の変数を設定することで、小数点以下の有効桁数や入出力の基数を指定することができます。

変数 意味
scale 小数点以下の有効桁数(初期値は0) scale=5 (小数点以下5桁を表示する)
obase 出力の基数(初期値は10) obase=16(入力する値を16進数に)
ibase 入力の基数(初期値は10) ibase=2(入力する値を2進数に)

ibase を指定すると、入力する際は(ibase の指定も)その基数で入力する必要があります。

以下は入出力の基数を指定する例です。

bc  return  #bc を起動
        
obase=2    return  #出力の基数を2進数に
255    return  #10進数の255を2進数に変換
11111111
10    return  #10進数の10を2進数に変換
1010

obase=16    return  #出力の基数を16進数に
255    return  #10進数の255を16進数に変換
FF
10   return  #10進数の10を16進数に変換
A

obase=10    return  #出力の基数を10進数に
ibase=2   return  #入力の基数を2進数に
111    return  #2進数の111を10進数に変換
7
101110+101    return  #2進数の計算結果を10進数に変換
51

ibase=1010  return  #入力の基数を10進数に
3+2   return  #10進数での計算
5

quit  return  #終了

コマンドラインから入力

以下は、コマンドラインから計算式を入力する書式です。

echo "計算式" | bc

小数点以下の桁数(scale)や基数(ibase obase)は必要に応じて毎回指定します。

複数の式を指定する場合は、セミコロン(;)で区切ります。

echo "3+4" | bc    return  #3+4 を計算
7

echo "scale=7;7/11"|bc    return  #小数点以下の桁数を7桁にして 7/11 を計算
.6363636

echo "sqrt(5)"|bc    return  #5の平方根(小数点以下の桁数を指定していない)
2

echo "scale=5;sqrt(5)"|bc    return  #小数点以下の桁数を5桁にして5の平方根
2.23606

echo "obase=16;254"|bc    return  #10進数の254を16進数に変換
FE

echo "ibase=2;1011001"|bc    return  #2進数を10進数に変換
89

echo "x=10;y=2;(x+y)*3"|bc    return  #変数と括弧を使った計算
36

スクリプトでの使用例

以下は、キーボードから2つの値を受け取って、割り算をして出力するスクリプトの例です。

キーボードからの入力は read コマンドで受け取り、変数 $x、$y に代入しています。

小数点以下5桁まで表示するように scale=5 を指定し、bc コマンドの実行結果はコマンド置換 ` ` を使って変数 $ans に代入しています。

bcTest
#! /bin/bash
read -p "Enter 2 numbers for division :> " x y
ans=`echo "scale=5; $x/$y" |bc`
echo $x / $y = $ans

以下は実行例です。

./bcTest    return
Enter 2 numbers for division :> 33 77   return #33 と 77 を入力
33 / 77 = .42857

制御構造

条件によって処理内容を変えたり、繰り返しなどの処理の記述を「制御構造」や「制御構文」と呼びます。

制御構造には、if 文や case 文、for 文、while 文などがあります。

if 文

if 文を使うと条件によって処理を切り分ける(分岐する)ことができます。

条件は「条件式」で表し、条件の判定には、test コマンドを使うことが多いです。

以下が基本的な書式です。if で始まり、if を逆さまにした fi で終わります。

# 条件が成立した場合にだけ実行する場合
            
if 条件式
then
  条件が成立する場合の処理
fi
# 条件が成立した場合と成立しなかった場合で処理を変える場合
            
if 条件式
then
  条件が成立する場合の処理
else
  条件が成立しない場合の処理
fi
# 複数の条件で処理を帰る場合
            
if 条件式1
then
  条件式1が成立する場合の処理
elif 条件式2
then
  条件式2が成立する場合の処理
else
  全ての条件が成立しない場合の処理
fi

if 文の改行

if 文では、if や then、else などのブロックの後は改行する必要があります。

改行しないで記述するにはセミコロン(;)を使用します。

また、空白を入れて読みやすいようにインデントすることができます。

前述の書式の例は以下のように書き換えることができます。

# 条件が成立した場合にだけ実行する場合
            
if 条件式 ; then
  条件が成立する場合の処理
fi

# 以下のように記述することもできます。
if 条件式 ; then 条件が成立する場合の処理; fi
# 条件が成立した場合と成立しなかった場合で処理を変える場合
            
if 条件式 ; then
  条件が成立する場合の処理
else
  条件が成立しない場合の処理
fi
# 複数の条件で処理を帰る場合
            
if 条件式1 ; then
  条件式1が成立する場合の処理
elif 条件式2 ;then
  条件式2が成立する場合の処理
else
  全ての条件が成立しない場合の処理
fi

コマンドラインで使用

if 文は主にシェルスクリプトで使用されますが、if などもコマンドなのでコマンドラインでも使用することができます。

コマンドを複数実行するにはセミコロンで区切って記述します。

# コマンドラインで1行に記述する場合の書式の例(処理の部分にコマンドを記述)
            
if 条件式 ; then 条件が成立する場合の処理; fi

if 条件式 ; then 条件が成立する場合の処理; else 条件が成立しない場合の処理; fi

コマンドラインで改行して入力することもできます。

その場合は、上記書式のセミコロン(;)の位置(スクリプトで改行している位置)で return を押して改行します(セミコロンは入力しません→セミコロンは改行の代わり)。

改行すると、2行目以降に > というプロンプトが表示されるので、その後に続きを入力し、fi の行の入力後、return を押すと処理が実行ます。

以下は if 文を使って「引数の数が 0の場合」は「引数がありません」、そうでなければ指定した引数の数を表示するスクリプト argsCount の例です。

引数の数は変数 $# に格納されています。

argsCount
#! /bin/bash
if [ $\# = 0 ]; then    
  echo "引数がありません"
else  
  echo "引数の数は${#}個です"
fi

以下はスクリプト argsCount に実行権を付与して、実行する例です。

chmod +x argsCount   return #実行権を付与
              
./argsCount   return #引数無しで実行
引数がありません

./argsCount foo  return #引数1つで実行
引数の数は1個です

./argsCount foo bar  return #引数2つで実行
引数の数は2個です

条件式

if 文は条件式に指定されたコマンドの終了ステータスを判定し、終了ステータスが 0 の場合は true(真)、0 以外の場合は false(偽)となり、一般的に条件式には test コマンドが使われます。

但し、test コマンドを使わなくても、条件式の部分が true(真)になれば(条件式に指定したコマンドが正常終了し終了ステータスが 0 になれば)then のブロックが実行されます。

以下は条件式に ls コマンドを使って引数で指定されたファイルやディレクトリを表示し、ls コマンドが成功(終了ステータスが 0)すれば「コマンド成功」と、失敗すれば「コマンド失敗」と表示するスクリプト(exprTest1)です。

8行目は、条件式と同じコマンドを実行して結果は全て /dev/null にリダイレクトして(出力は必要ないので)、9行目で ls -l コマンドの終了ステータスを出力しています。

exprTest1
#! /bin/bash
if ls -l "$1"; then
  echo "コマンド成功"
else
  echo "コマンド失敗"
fi

ls -l "$1" >/dev/null 2>&1
echo "終了ステータス: $?"

以下は実行例です。

bash exprTest1 sample.txt   return #存在するファイルを指定して実行
-rw-r--r--@ 1 foo  staff  519  9  9 08:47 sample.txt
コマンド成功
終了ステータス: 0

bash exprTest1 testX   return #存在しないファイルを指定して実行
ls: testX: No such file or directory
コマンド失敗
終了ステータス: 1

以下は前述のスクリプト exprTest1 の if 文の部分とほぼ同様のことをコマンドラインで実行する例です。

var="sample.txt"   return #変数に存在するファイルを格納
            
if ls -l $var; then echo "コマンド成功"; else echo "コマンド失敗" ; fi  return 
#以下は上記 if 文を実行した結果の出力
-rw-r--r--@ 1 foo  staff  519  9  9 08:47 sample.txt
コマンド成功

#以下はコマンドラインで改行して実行する例です。
if ls -l $var     return #改行(2行目からプロンプト > が表示される)
> then echo "コマンド成功"    return #改行
> else echo "コマンド失敗"    return #改行
> fi   return #実行

test コマンドの演算子

条件式に test コマンドの演算子を使えば、様々な条件を設定することができます。

以下は引数に指定されたファイルが存在しなければ「指定したファイルが存在しません」と表示し、存在すれば、その内容を cat コマンドで表示するスクリプト ifTest1 です。

ファイルが存在するかどうかは、ファイルが存在する場合に真(0)を返す -f 演算子と条件を反転させる ! 演算子と使って、「ファイルが存在しない場合」という条件を設定しています。

また、変数($1 と $file)はダブルクォートで囲んでいます(変数と引用符)。

ifTest1
#! /bin/bash
file="$1"   #引数を変数に代入
if [ ! -f "$file" ] ; then    #引数に指定したファイルが存在しない場合
  echo "指定したファイルが存在しません"
else 
  echo "ファイル「${1}」が見つかりました。以下が内容です"
  cat "$file"
fi

以下は実行例です。

bash ifTest1 myscript   return #存在するファイルを指定して実行
ファイル「myscript」が見つかりました。以下が内容です
#!/bin/bash
myvar="my variable"
echo "myvar: $myvar"
echo "PATH: $PATH"
echo "LANG: $LANG"

bash ifTest1 myscriptX   return #存在しないファイルを指定して実行
指定したファイルが存在しません

以下は指定された引数が2つの場合は、引数に指定された文字列を比較して値が同じ場合は「2つの引数は等しいです」、異なる場合は「2つの引数は等しくありません」と表示するスクリプト ifTest2 です。

もし指定された引数の数が2でない場合は「引数は2つ指定してください」と表示します。

以下の例の場合、「引数が2つの場合」に更に条件を分岐するので、if 文の中に if 文を記述(ネスト)しています。

ifTest2
#! /bin/bash
if [ $\# = 2 ]; then
    if [ $1 = $2 ]; then
      echo "2つの引数は等しいです"
    else
      echo "2つの引数は等しくありません"
    fi
  else
    echo "引数は2つ指定してください"
fi

以下はスクリプト ifTest2 の実行例です。

bash ifTest2 abc efg  return
2つの引数は等しくありません

bash ifTest2 abc abc  return
2つの引数は等しいです

bash ifTest2 abc  return
引数は2つ指定してください

bash ifTest2 abc 123 xyz  return
引数は2つ指定してください
変数と引用符

シェルスクリプトやコマンドラインでは、文字列を引用符で囲むことは必須ではありません。

また、変数を参照する際にもダブルクォート("")で囲むことは必須ではありません。

一般的には、変数が空であったり任意の空白(スペース等)や特殊文字(ワイルドカード)を含む(可能性がある)場合はダブルクォートで囲みます。

変数がダブルクォートで囲まれていないと、エラーになって実行できなくなる場合もあるので、原則として変数はダブルクォートで囲んでおくと安全です。

関連項目:シェルの展開順序

以下は変数($1 と $file)をダブルクォートで囲んでいないスクリプトの例です。

ifTest1_ng
#! /bin/bash
file=$1
if [ ! -f $file ] ; then
  echo "指定したファイルが存在しません"
else 
  echo "ファイル「${1}」が見つかりました。以下が内容です"
  cat $file
fi

このスクリプトを引数なしで実行すると、以下のように表示され入力待ちの状態になってしまいます。

引数の指定がないので、[ ! -f ] の部分は偽(1)となり、else ブロックに移り、cat コマンドが引数なしで実行されて入力待ちになっている状態です。この場合、control + d で終了しなければなりません。

bash ifTest1_ng   return #引数を指定せずに実行
ファイル「」が見つかりました。以下が内容です

また、引数に空白を含む文字列を指定してもエラーになってしまい、期待した結果にはなりません。

bash ifTest1_ng "sample test.txt"  return #引数に空白を含む文字を指定して実行
ifTest1_ng: line 3: [: sample: binary operator expected   #エラー 
ファイル「sample test.txt」が見つかりました。以下が内容です
cat: sample: No such file or directory

スクリプトの中の変数をダブルクォートで囲んでおくと、上記のようなエラーを防ぐことができます。

以下は変数($1 と $file)をダブルクォートで囲んであるスクリプトです。

ifTest1
#! /bin/bash
file="$1"   #変数ダブルクォートで囲む
if [ ! -f "$file" ] ; then    #変数ダブルクォートで囲む
  echo "指定したファイルが存在しません"
else 
  echo "ファイル「${1}」が見つかりました。以下が内容です"
  cat "$file"    #変数ダブルクォートで囲む
fi

変数をダブルクォートで囲んでおくと、前述の例と同じ引数で実行してもエラーにはなりません。

bash ifTest1   return #引数を指定せずに実行
指定したファイルが存在しません

bash ifTest1 "sample test.txt"  return #引数に空白を含む文字を指定して実行
指定したファイルが存在しません

エラー test: ==: unary operator expected

以下は [ ] コマンドで変数の値を比較する際に、変数の値が未定義であったり、空の場合にエラーになる例です。

foo="foo"   return #変数 foo に値を代入(設定)
[ $foo != $bar ]; echo $?   return #変数 foo と未定義の bar を [ ] コマンドで比較判定
-bash: [: foo: unary operator expected    #変数 bar が未定義なのでエラー
2

[ $foo != "$bar" ]; echo $?   return #変数 bar をダブルクォートで括るとエラーにならない
0

bar=""   return #変数 bar に空文字を代入
[ $foo != $bar ]; echo $?   return #変数 bar をダブルクォートなしで比較判定
-bash: [: foo: unary operator expected    #変数 bar が空文字の場合もエラー
2

[ $foo != "$bar" ]; echo $?   return #再度変数 bar をダブルクォートで括るとエラーにならない
0

foo=""   return #変数 foo に空文字を代入
[ $foo != "$bar" ]; echo $?   return #変数 foo をダブルクォートなしで比較判定
-bash: [: !=: unary operator expected    #変数 foo が空文字の場合なのでエラー
2

[ "$foo" != "$bar" ]; echo $?  return #両方ともダブルクォートで括るとエラーにならないで済む
1
case 文

case 文は、文字列のパターンで処理を分岐させる制御構造です。

指定した値とパターン(または文字列)を照合してそれらがマッチした場合に処理を実行します。

以下が基本的な書式です。case で始まり、case を逆さまにした esac で終わります。

パターンは必要なだけ指定することができ、パターンと処理は「)」で区切り、それぞれの処理の最後に「;;」を記述します(最後の「;;」は省略可能)。

casein
  パターン1) 値がパターン1にマッチした場合の処理 ;;
  パターン2) 値がパターン2にマッチした場合の処理 ;;
  ・・・
  パターンn) 値がパターンnにマッチした場合の処理 ;;
esac

また、処理で複数のコマンドを実行する場合は、改行または「;」で区切ります。

casein
  パターン1) 
    値がパターン1にマッチした場合のコマンド1
    値がパターン1にマッチした場合のコマンド2 
    値がパターン1にマッチした場合のコマンド3 ;;
  パターン2) 
    値がパターン2にマッチした場合のコマンド1
    値がパターン2にマッチした場合のコマンド2 
    値がパターン2にマッチした場合のコマンド3 ;;
  ・・・
esac

case 文は上から順に値とパターンの照合を行ってパターンとマッチした場合にそのパターンと共に指定されている処理を実行します。

最初にマッチしたパターンで指定されている処理を実行したら終了します。

もし、いずれのパターンにもマッチしない場合は、何も実行されません。

どの文字にもマッチするワイルドカード「*」を使ったパターンを最後に指定して、いずれのパターンにもマッチしなかった場合のための処理を指定することもできます。

# どのパターンにもマッチしなかった場合の処理を指定する場合
            
casein
  パターン1) 値がパターン1にマッチした場合の処理 ;;
  パターン2) 値がパターン2にマッチした場合の処理;;
  ・・・
  パターンn) 値がパターンnにマッチした場合の処理 ;;
  *) 値がどのパターンにもマッチしなかった場合の処理 ;;
esac

以下は、case の値に $1(一番目の引数)を指定して、スクリプトの引数に「y」を指定したら「Yes」、「n」を指定したら「No」、それ以外を指定したら「Something else」と表示するスクリプトです。

caseTest1
#! /bin/bash
case "$1" in
  "y") echo "Yes";;
  "n") echo "No" ;;
  *) echo "Something else" ;;
esac

上記のスクリプトの場合、パターン部分に指定した文字列 "y" と "n" の引用符は省略することができます。

ワイルドカードの * は引用符で囲むと文字としての "*" にしかマッチしないので引用符で囲みません。

以下は実行例です。

chmod +x caseTest1    return #スクリプトに実行権を付与
              
$ ./caseTest1 y    return #引数に y を指定して実行
Yes

$ ./caseTest1 n    return #引数に n を指定して実行
No

$ ./caseTest1 s    return #引数に s を指定して実行
Something \else

パターンの指定

パターンの指定にはワイルドカードや OR 条件(|)を使用することができます。

以下は前述のスクリプトをワイルドカードの [ ] を使って、y と n の大文字・小文字のどちらでも指定された場合にマッチするように変更した例です。

caseTest2
#! /bin/bash
case "$1" in
  [yY]) echo "Yes";;   #ワイルドカードを使って y と Y どちらでもマッチ
  [nN]) echo "No" ;;   #ワイルドカードを使って n と N どちらでもマッチ
  *) echo "Something else" ;;
esac

以下は「または」を意味する OR 条件の | を使って、前述の例と同様、y と n の大文字・小文字のどちらでも指定された場合にマッチするようにした例です。

caseTest3
#! /bin/bash
case "$1" in
  "y"|"Y") echo "Yes";;
  "n"|"N") echo "No" ;;
  *) echo "Something else" ;;
esac

以下はワイルドカードの [ ] と OR 条件の | を組み合わせて、パターンを y や Y または yes Yes YEs YES yEs yES のような文字列にマッチするように変更した例です。

[yY][eE][sS] は以下のような意味になります。

  • 1文字目: y または Y
  • 2文字目: e または E
  • 3文字目: s または S
caseTest4
#! /bin/bash
case "$1" in
  [yY]|[yY][eE][sS]) echo "Yes";;
  [nN]|[nN][oO]) echo "No" ;;
  *) echo "Something else" ;;
esac

以下は実行例です。

./caseTest4 Y     return
Yes
./caseTest4 yeS   return
Yes
./caseTest4 YES   return
Yes
./caseTest4 nO    return
No
./caseTest4 NO    return
No

条件の値を予め大文字または小文字に変換

前述のように大文字や小文字の組み合わせのパターンを増やすのではなく、条件の値を予め大文字または小文字に変換した上で比較する方法があります。

その1つは tr コマンドを使って case に指定した値を大文字または小文字に変換する方法です。

tr a-z A-Z を実行すると小文字を大文字に変換し、tr A-Z a-z を実行すると大文字を小文字に変換します。

tr コマンドは標準入力が使われるので、この例の場合は echo コマンドの出力をパイプ | で tr コマンドに読み込ませます。

また、変換した文字列を case 文の値に指定するには、コマンド置換 ` ` を使ってコマンドをコマンドの出力で置き換えます。

caseTest5
#! /bin/bash
case `echo "$1" | tr A-Z a-z` in   ##引数 $1 を小文字に変換
  "y"|"yes") echo "Yes";;
  "n"|"no") echo "No" ;;
  *) echo "Something else" ;;
esac

キーボードからの入力を判定

以下は read コマンドを使って、スクリプトの引数を判定(照合)する代わりにキーボードからの入力を照合するように書き換えた例です。

前述の例と同様、条件の値(キーボードからの入力)は予め tr コマンドを使って小文字に変換しています。

caseTest6
#! /bin/bash
read -p "yes or no: > " input    # キーボードからの入力を変数 input に格納
case `echo "$input" | tr A-Z a-z` in    # $input を小文字に変換してから照合
  "y"|"yes") echo "Yes";;
  "n"|"no") echo "No" ;;
  *) echo "Something else" ;;
esac

以下は実行例です。

read コマンドの -p オプションで「yes or no: >」というプロンプトを表示しています。

./caseTest6    return
yes or no: > y     return # y と入力
Yes
./caseTest6    return
yes or no: > YES   return # YES と入力
Yes
./caseTest6    return
yes or no: > NO    return # NO と入力
No
./caseTest6    return
yes or no: > nO   return # nO と入力
No
./caseTest6    return
yes or no: > yesno return # yesno と入力
Something else
for 文

for 文は一定回数の繰り返し処理を行わせる制御構造です。

以下が基本的な書式です。値のリストの数だけ処理が実行されます。

for 変数 in 値のリスト
do
  処理
done

for 文は変数に値のリストで指定した値を順番に代入し、それぞれに対して do 〜 done の間の処理を繰り返し、全てを実行したら終了します。

  1. 値のリストに指定された1つ目の値を変数に代入し、do 〜 done 間の処理を実行
  2. 値のリストに指定された2つ目の値を変数に代入し、do 〜 done 間の処理を実行
  3. 値のリストに指定された3つ目の値を変数に代入し、do 〜 done 間の処理を実行
    以降同様に処理を実行し、リストの全ての値を処理したら終了。

変数

for 文に指定する変数は、任意の変数を使用することができます。

値のリスト

値のリストには文字列や数値のリストを直接指定することも、変数やコマンド置換を使ったコマンドの実行結果、ブレース展開配列、ファイルなどを指定することができます。

また、for はコマンドなので、コマンドラインでも使用することができます。

以下は1行で入力する場合の書式です。

for 変数 in 値のリスト; do コマンド; done

コマンドラインでもセミコロンの代わりに、return を押して改行して実行することもできます。

改行すると、2行目以降にプロンプト(>)がされるので、プロンプトの後に続きを入力して最後に done を入力し return を押すと処理が実行されます。

以下は、値のリストに記述されている数値を変数 i に読み込んでそれらを出力するスクリプトです。

forTest1
#! /bin/bash
for i in 1 2 3 4 5   # 変数:i 値のリスト: 1 2 3 4 5 
do
  echo $i   # 変数 $i に読み込まれた値を出力
done

以下はスクリプトに実行権を付与してからスクリプトを実行した結果の例です。

変数 i には繰り返し処理ごとに1から5までの値が代入されて、 do 〜 done の間の echo により順番に合計5回出力されます。

chmod +x forTest1 return  # 実行権を付与
            
./forTest1  return  # スクリプトを実行
1
2
3
4
5

以下は上記のスクリプトと同じことをコマンドラインで実行した例です。

1行目はセミコロンを使って1行で記述して実行した例で、8〜10行目はコマンドラインで改行して実行した例です。

for i in 1 2 3 4 5; do echo $i; done return # 実行
1
2
3
4
5

$ for i in 1 2 3 4 5 return # 改行
> do echo $i         return # 改行
> done               return # 実行
1
2
3
4
5

また、bash では他のプログラム言語と同じような以下の書式も使うことができます。

但し、条件式などを囲む括弧は2つ (( )) で囲みます。

for ((初期化式; 繰り返し条件式; 増減式))
do
  処理
done

以下は前述のスクリプト forTest1 を上記の書式で書き換えた例です。

for と (、( と i、+ と ) の間やセミコロンの後にはスペースがあっても問題ありませんが、括弧 (( や )) の間にスペースがあるとエラーになります。

forTest2
#! /bin/bash
for ((i=1; i<=5; i++))
do
  echo $i
done

以下は上記と同じことをコマンドラインで実行した例です。

for((i=1;i<=5;i++)); do echo $i; done return
1
2
3
4
5

変数を値のリストに指定

以下は引数の値をリストに指定して、順番に出力するスクリプトです。

引数のリストは変数(位置パラメータ)$@ に格納されています。

forTest3
#! /bin/bash
for param in "$@"   #値のリストに引数のリストを指定
do
  echo $param
done

以下は実行例です。

./forTest1 "apple pie" "orange juice" "grape soda" return
apple pie
orange juice
grape soda

スクリプト forTest3 では値のリストに変数を指定する際にダブルクォートで囲んでいます。

もし、以下のようにダブルクォートで囲んでいない場合は、実行結果が異なります。

forTest3_2
#! /bin/bash
for param in $@  #変数をダブルクォートで囲んでいない
do
  echo $param
done

変数の値が空白を含む文字列の場合、以下のように空白により分割されて個別の引数として扱われてしまいます。

./forTest3_2 "apple pie" "orange juice" "grape soda"
apple
pie
orange
juice
grape
soda

コマンドの実行結果を値のリストに指定

値のリストには、コマンドの実行結果を指定することができます。

コマンドの実行結果を指定するには、コマンド置換 ` ` を使ってコマンドをコマンドの出力で置き換えます。

以下はカレントディレクトリにある全てのテキストファイルのサイズを大きい順に表示するスクリプトです。

forTest4
#! /bin/bash
for file in `ls -S *.txt`
do
  echo "Size(byte): `wc -c $file`"
done

ls -S *.txt はカレントディレクトリの全テキストファイルのファイル名をサイズの大きい順に出力します。

ls -S *.txt  return # ls -S コマンドの実行例
cs_sample.txt		sample.txt		test.txt
system_log.txt		info.txt		sample2.txt		
sample_x.txt		process.txt

それぞれのファイル名は変数 $file に格納されて、do 〜 done 間で処理されます。

wc コマンドは -c オプションを指定するとファイルのバイト数を表示します。echo コマンドの中で展開したいので、`wc -c $file`のようにコマンド置換を使っています。

以下は実行例です。

./forTest4  return # スクリプトを実行
Size(byte):    62297 cs_sample.txt
Size(byte):    59199 system_log.txt
Size(byte):      913 sample2.txt
Size(byte):      519 sample.txt
Size(byte):      417 info.txt
Size(byte):      111 sample_x.txt
Size(byte):       91 process.txt
Size(byte):       22 test.txt

※カレントディレクトリのファイルを順に処理するような場合などでは、ls コマンドではなくパス名展開(*)を利用することができます。

以下はコマンドラインでの実行例です。

for var in *
> do
> echo "$var"  # ダブルクォートで囲む
> done

for var in *.txt
> do
> echo "$var" # ダブルクォートで囲む
> done

#以下でも同じ結果になりますが、ある意味冗長
for var in "`ls`"  # ダブルクォートで囲む(空白で分割されないように)
> do
> echo "$var"  # ダブルクォートで囲む
> done

for var in "`ls *.txt`" # ダブルクォートで囲む(空白で分割されないように)
> do
> echo "$var" # ダブルクォートで囲む
> done

seq コマンドの利用

seq コマンドは、連続する番号を出力するコマンドです。

seq コマンドで連番を作成して値のリストに指定し、ループ(繰り返し)処理を実行することができます。

この場合も、コマンドの実行結果を値のリストに指定するのでコマンド置換を使います。

以下は seq コマンドで1〜5の連番を生成して、echo を5回繰り返し実行するスクリプトです。

forTest5
#! /bin/bash
for n in `seq 5`
do
  echo "$n 回目の処理"
done

以下は実行例です。

./forTest5  return
1 回目の処理
2 回目の処理
3 回目の処理
4 回目の処理
5 回目の処理

以下は、seq コマンドで連続した2桁の番号を含むファイル名(sample_01.txt〜sample_05.txt)を生成し、touch コマンドでカレントディレクトリに空のテキストファイルを作成するスクリプトです。

変数 $fn には seq コマンドで生成したファイル名が入ります。

forTest6
#! /bin/bash
for fn in `seq -f "sample_%02g.txt" 5`
do
  touch $fn
done

以下は read コマンドでユーザからの入力を読み取り、変数に代入してその値を元に空のテキストファイルを作成するスクリプトです。

read コマンドでは -p オプションでプロンプトを表示し作成するファイルの数(Number)とファイルの名前の先頭部分の文字列(Prefix)を入力するように促します。

作成するファイルの数は変数 $num に代入し、seq コマンドの引数に指定します。

seq コマンドで作成されるファイル名(01.txt など)は変数 $fn に代入されます。

ファイルの名前の先頭部分の文字列は変数 $pre に代入し、touch コマンドでファイルを作成する際にファイル名の先頭に追加します。

forTest7
#! /bin/bash
read -p "Enter Number and Prefix > " num pre
for fn in `seq -f "%02g.txt" $num`
do 
  touch $pre$fn
done

以下は実行例です。

ls  return #ディレクトリが空なのを確認

./forTest7  return #スクリプトを実行(以下のプロンプトが表示される)
Enter Number and Prefix > 3 sample-  return #ファイル数と文字列を入力

ls -l  return #以下のファイルが作成されている
total 0
-rw-r--r--  1 foo  staff  0 10  3 10:56 sample-01.txt
-rw-r--r--  1 foo  staff  0 10  3 10:56 sample-02.txt
-rw-r--r--  1 foo  staff  0 10  3 10:56 sample-03.txt

ブレース展開を値のリストに指定

ブレース展開を値のリストに指定することもできます。

以下はコマンドラインでの実行例です。

for fname in sample{1..5}.jpg  return #コマンドラインで実行
> do          return
> echo $fname return
> done        return
sample1.jpg
sample2.jpg
sample3.jpg
sample4.jpg
sample5.jpg

配列を値のリストに指定

配列を値のリストに指定することもできます。

${配列名[@]} で全ての値を出力することができるので、これを値のリストに指定します。

以下はコマンドラインでの実行例です。

array=("apple" "banana" "mango")  return #配列を生成

for elm in ${array[@]}  return #コマンドラインで実行 ${配列名[@]} を値のリストに指定
> do
> echo $elm
> done 
apple
banana
mango

ファイルから読み込む

cat コマンドなどを使ってファイルの内容を読み込ませて、値のリストに指定することもできます。

この例では以下のようなテキストファイル(sample.txt )を読み込ませます。

cat sample.txt  return
First Line
Second Line
Third Line

Fifth Line (Fourth Line is empty)

コマンドの実行結果を値のリストに指定するのでコマンド置換を使います。

但し、cat コマンドはファイルを1行ずつ読み出すのではないので、ファイルの内容や「値のリストに指定するコマンドの実行結果」や「変数をダブルクォートで囲むかそうでないか」で実行結果が変わってきます。

ファイルの内容を1行ずつ読み込んで処理をする場合は、while 文を使います。

以下は、コマンドの実行結果 `cat sample.txt` と変数 $line をダブルクォートで括る例です。

forTest8
#! /bin/bash
for line in "`cat sample.txt`"
do
  echo  "$line"
done

以下が実行結果です。

./forTest8 return
First Line
Second Line
Third Line

Fifth Line (Fourth Line is empty)

以下は、変数 $line のみをダブルクォートで括る例です。

forTest8-2
#! /bin/bash
for line in `cat sample.txt`   #コマンドの実行結果のダブルクォートを外す
do
  echo  "$line"
done

以下が実行結果です。for に渡される際にファイルの内容が空白や改行で分割され、それぞれ処理されます。

./forTest8-2 return #
First
Line
Second
Line
Third
Line
Fifth
Line
(Fourth
Line
is
empty)

以下のように繰り返しをカウントして確認するとわかるのですが、最初の例(forTest8)では for は1回しか繰り返しを行っていません。

変数($line)をダブルクォートで括ってあるので、読み込んだテキストファイルの改行が反映されて各行が改行されて表示されています(変数 $line をダブルクォートで括らないと全て1行で出力されます)。

((count++)) は let コマンドを使ってカウント(count 変数)の値を処理のたびに1増加させています。

forTest8(カウンタを追加)
#! /bin/bash
count=0   #繰り返しをカウントする変数
for line in "`cat sample.txt`"
do
  ((count++))   #繰り返しごとに1増加
  echo  "$line"
  echo "$count"
done

コマンドの実行結果 `cat sample.txt` をダブルクォートで括った上記の場合の実行結果は以下のようになり、変数 $line にはファイルの内容が丸ごと一回渡されただけでした。

./forTest8 return 
First Line
Second Line
Third Line

Fifth Line (Fourth Line is empty)
1

コマンドの実行結果 `cat sample.txt` をダブルクォートで括らないと以下のような出力になります。

./forTest8 return 
First
1
Line
2
Second
3
Line
4
Third
5
Line
6
Fifth
7
Line
8
(Fourth
9
Line
10
is
11
empty)
12

以下は、カレントディレクトリにある sample から始まるテキストファイルのファイル名と内容を表示するスクリプトです。

カレントディレクトリのファイルを順に読み込むには、リストに指定する際にパス名展開を使っています。

do~done 間では、echo でファイル名を、cat でファイルの内容を表示しています。

forTest9
#! /bin/bash
for file in sample*.txt
do
  echo "File Name:$file"
  cat "$file"
  echo "End of: $file"
done

以下は実行例です。「sample*.txt」にマッチする2つのファイルの名前と内容が表示されています。

./forTest9 return
File Name:sample.txt
First Line
Second Line
Third Line

Fifth Line (Fourth Line is empty)
End of: sample.txt
File Name:sample2.txt
this is sample2.
abcdefg
1234
End of: sample2.txt
while 文

while 文は「条件が成り立っている間(指定した条件の判定が true である限り)、処理を繰り返す」制御構造です。

while 文は指定された条件を判定(評価)し、結果が true である場合のみ繰り返し処理を継続します。

繰り返す度に条件を判定して true であれば処理を実行し、条件の判定結果が false になった時点で処理を終了します。

通常 for 文は処理する回数が明確な場合に使用し、処理する回数が明確ではない場合に while 文を使用します。

以下が基本的な書式です。

条件を判定して結果が true であれば、do〜done の間に記述された処理を実行します。

while 条件
do
  処理
done

while 文はコマンドラインでも使用できます。以下が基本的なコマンドラインでの書式です。

処理でコマンドを複数記述する場合は、コマンドをセミコロンで区切ります。

while 条件 ; do 処理 ;done

コマンドラインでもセミコロンの代わりに、return を押して改行して実行することもできます。

改行すると、2行目以降にプロンプト(>)がされるので、プロンプトの後に続きを入力して最後に done を入力し return を押すと処理が実行されます。

条件には test コマンド の [ ] やその他のコマンドなどを記述します。

以下は条件に test コマンド [ ] に数値を比較する演算子 -le(左辺が右辺より小さいか等しい場合に true を返す)を指定して1〜5を表示するスクリプトです。

最初に数値を格納する変数 i に1を代入しておきます。

条件 [ $i -le 5 ] は $i の値が5以下の場合は true を返し、5より大きい場合は false を返します。

条件が true の場合は do〜done の間に記述されている echo "$i" により変数 $i の値が表示さ、続いて ((i++)) が実行されます。

((i++)) は let コマンドのインクリメント演算子を使って変数 $i の値を1増加させています。

(( )) は let コマンドの別の書式ですが、let i++ と記述することもできます。

((i++)) は ((i=i+1)) と記述するのと同じことです。

また、((i++)) は i=$((++i)) と前置インクリメントを使って記述することもできますが、この場合後置インクリメントを使って i=$((i++)) とすると i の値は1から変わらず無限ループになってしまいます。

whileTest1
#! /bin/bash
i=1
while [ $i -le 5 ]
do 
  echo "$i"
  ((i++))  #または let i++  または ((i=i+1)) または i=$((++i))
done

以下は実行例です。

./whileTest1 
1
2
3
4
5

let コマンド (( )) は bash では使えますが、互換性を考慮して expr コマンドを使って記述すると以下のようになります。

expr コマンドは結果を標準出力に出力するので、計算結果を変数に入れるにはコマンド置換を使います。

但し、expr コマンドは処理が遅いので (( )) を使用した方が処理が速いです。

whileTest1-2
#! /bin/bash
i=1
while [ $i -le 5 ]
do 
  echo "$i"
  i=`expr $i + 1`  # expr コマンドで i の値を増加
done

以下は条件にも let コマンドの (( )) の書式を使う場合の例です。

(( )) の中の式では演算子 <= を使って条件を指定し、左右の値の大小の比較をして判定しています。

whileTest1-3
#! /bin/bash
i=1
while ((i<=5))
do 
  echo "$i"
  ((i++)) #または let i++
done

以下は前述のスクリプトをコマンドラインで実行する例です。

i=1; while ((i<=5)); do echo "$i"; ((i++)); done return
1
2
3
4
5

#以下は return で改行して入力する場合の例

i=1               return
while ((i<=5))    return
> do              return
> echo "$i"       return
> ((i++))         return
> done            return
1
2
3
4
5

前述の例のように実行する回数が決まっている場合は、for 文を使って以下のように記述しても同じになります。

#! /bin/bash
for i in `seq 5`
do
  echo "$i"
done

#または
#! /bin/bash
for ((i=1; i<=5; i++))
do
  echo "$i"
done

以下は、キーボードからの入力を read コマンドで読み取って入力された文字列を表示し、「no」が入力された場合は終了するスクリプトです。

条件には、read コマンドで読み込んだ値が格納されている変数 $str の値が no という文字列でないかを [ ] コマンドで判定しています。

その際、変数 $str はダブルクォートで括るか、事前に変数 $str に空ではない文字列(初期値)を設定しておく必要があります。

基本的に変数はダブルクォートで括っておくと安全です。関連項目:変数と引用符

whileTest2
#! /bin/bash
while [ "$str" != "no" ]
do 
  read -p "Enter a word or 'no' to stop: > " str
  echo You enter: "$str"
done

以下は実行例です。

このスクリプトの場合、no と入力された後、その文字列を表示し、条件に戻った際に false になり終了します。

./whileTest2       return
Enter a word or 'no' to stop: > abc       return # abc と入力
You enter: abc
Enter a word or 'no' to stop: > no       return # no と入力
You enter: no

無限ループと break

条件にヌルコマンド : (コロン)を指定すると、ヌルコマンドは何もせず終了ステータスが 0(true)になるので、条件が true となり続け無限ループになります。

ループを抜けるには ctrl + c で強制的に終了するか、while 文の中にループを終了する break コマンドを記述します。

ヌルコマンドの代わりに true と記述しても条件が常に true になるので、無限ループになります。

以下は、条件にヌルコマンド : (コロン)を指定して無限ループにして、処理の中でキーボードからの入力を読み取り、if 文を使ってその値が「no」の場合は break コマンドでループを終了するスクリプトです。

break コマンドは for 文や while 文などで使われるループを抜けるためのコマンドです。実行すると、その時点で現在のループから抜けてそのループ内の以降の処理を実行しません。

whileTest3
#! /bin/bash
while :     # または while true でも同じ
  do
    read -p "Enter a word or 'no' to stop:> " str   # 入力された文字を変数 str に格納
    if [ "$str" != "no" ]   # 入力された文字が no でなければ、その文字を表示
    then
      echo You enter: "$str"
    else   # 入力された文字が no であれば「Stopped」と表示して break で終了
      echo "Stopped"
      break
    fi
  done
echo "done の後の echo"  #ループを抜けた後はここに来る

以下が実行例です。

このスクリプトの場合、no と入力された後「Stopped」と表示し、break コマンドでループの処理(do〜done)を抜け、done の次の行に移り「done の後の echo」と表示して終了します。

./whileTest3     return
Enter a word or 'no' to stop:> abc       return # abc と入力
You enter: abc
Enter a word or 'no' to stop:> 123       return # 123 と入力
You enter: 123
Enter a word or 'no' to stop:> no     return # no と入力
Stopped
done の後の echo  #ループを抜けた後に最後の行が実行され、それ以降何もないので終了

条件にヌルコマンド(:)を指定しなくても、条件に指定したコマンドの実行が成功している間は処理を繰り返すことができます。

以下は前述の例をヌルコマンドを使わずに書き換えた例で、動作はほぼ同じです。

2行目は、ループの前に記述してあるので、初回のみ実行され、前述の例の read -p でのプロンプト表示のようにするため、echo コマンドに -n オプリョンを指定して出力の最後に改行しないようにしています。

条件には read コマンドを指定しているのでキーボードから文字が入力され、return キーが押されればコマンドは成功するので処理が繰り返されます。

キーボードから no が入力されると、break コマンドで終了します(前述の例と同じ)。

前述の例と異なるのは、 control + d が押されると EOF(End Of File)と言う終了を示す信号が送られるので、その場合は read コマンドは失敗するので、その時点で while 文は終了します。

#! /bin/bash
echo -n "Enter a word or 'no' to stop:> "   # -n で最後に改行を出力させない
while read str       #条件に read コマンドを指定
  do
    if [ "$str" != "no" ]
    then
      echo you enter: "$str"
    else
      echo "Stopped"
      break
    fi
    echo -n "Enter a word or 'no' to stop: >"
  done
echo "done の後の echo "

continue

continue コマンドを使うと、処理を中断して(それ以降の処理をスキップして)ループの先頭に移動することができます。

break コマンドではループから抜けてしまうのでそのループは終了してしまいますが、continue コマンドではループは終了せず、それ以降の処理をスキップしてループの先頭に戻ります。

以下は case 文を使って、y と入力されたら「You entered: y」と表示し、n と入力されたら「You entered: n」「Exiting」と表示して break コマンドでループを抜け、それ以外の文字が入力されたら「?」と表示して continue コマンドでループの先頭に戻るスクリプトです。

whileTest4
#! /bin/bash
while :   # ヌルコマンドで無限ループに
  do
    read -p "Enter y or n:> " str
    case "$str" in
      "y") echo "You entered: y";;
      "n") echo "You entered: n"; echo "Exiting"; break;; # ループを抜けて done の次へ
       * ) echo "?"; continue;;  # ループの先頭に戻る(y と n 以外が入力された場合)
    esac
    echo "Thank you!"    # y と入力した場合には表示される
  done
echo "bye"    # n と入力してループを抜けると表示される

10行目の echo が実行されるのは「y」と入力された場合のみで、「y」や「n」以外が入力された場合は continue コマンドでループの先頭に戻るので表示されません。

12行目の echo が実行されるのは「n」と入力されて break コマンドが実行され、ループを抜ける場合のみです。

以下が実行例です。

./whileTest4       return
Enter y or n:> y       return # y を入力
You entered: y
Thank you!    # スクリプトの10行目
Enter y or n:> x       return # x を入力
?
Enter y or n:> n       return # n を入力
You entered: n
Exiting
bye   # スクリプトの12行目 

ファイルから読み込む

read コマンドは標準入力から文字列を読み込むので、リダイレクトまたはパイプを使ってファイルを読み込んで表示することができます。

以下の例では次のような行頭に空白(スペースやタブ)が入ったテキストファイル(sample.txt)を読み込みます。

sample.txt
First Line
  Second Line
 Third Line

Fifth Line (Fourth Line is empty)
	Sixth Line \n

リダイレクト

以下はリダイレクトを使って sample.txt を読み込んで1行ずつ表示するスクリプトです。

< ./sample.txt でリダイレクトを使ってカレントディレクトリにある sample.txt を読み込んでいます。

whileTest5
#! /bin/bash
while read line
do
  echo "$line"
done < ./sample.txt

読み込んだファイルを while read line でファイルの終了(EOF)になるまで、各行を変数 line に格納して表示しています。

変数名を line とすることが多いですが、任意の変数名を使うことができます。

read コマンドは標準入力から1行読み取ってその値を変数に代入するので、その変数を出力すればファイルの内容を表示することができます。

また、ファイルの終了(EOF)で read コマンドはそれ以上行を読み込めないのでコマンドは失敗し、while 文が終了します。

以下は実行結果の例です。行頭のスペースやタブ、エスケープ文字のバックスラッシュが取り除かれています。

./whileTest5    return
First Line
Second Line
Third Line

Fifth Line (Fourth Line is empty)
Sixth Line n

エスケープ文字

前述の実行例を見ると、最後の行のバックスラッシュ\(エスケープ文字)が消えています。

エスケープ文字を解析せずにそのまま読み込むには以下のように、read コマンド-r オプションを指定します。

#! /bin/bash
while read -r line   # read コマンドに -r オプリョンを指定
do
  echo "$line"
done < ./sample.txt

行頭の空白(スペースやタブ)

また、前述の実行例では、テキストファイルにあった行頭のスペースやタブが read コマンドによりなくなっています。

これは read コマンドの行区切りや区切り文字の認識に使われる IFS(Internal Field Separator)のデフォルトの値に含まれるスペースとタブによります。

行頭や行末の空白(スペースやタブ)を取り除かれないようにするには、IFS= (一時的に IFSの値に何も指定しない)を指定して read コマンドを実行します。

「IFS=」の後に、セミコロンを付けないようにします。

whileTest5_1
#! /bin/bash
while IFS= read  line    # read コマンドの前に IFS= と指定
do
  echo "$line"
done < ./sample.txt

または、以下のように明示的に IFS=null と記述しても同じです。

#! /bin/bash
while IFS=null read  line    # read コマンドの前に IFS=null と指定
do
  echo "$line"
done < ./sample.txt

以下は実行例です。行頭のスペースやタブが取り除かれていません。

./whileTest5_1    return
First Line
  Second Line
 Third Line

Fifth Line (Fourth Line is empty)
	Sixth Line n

区切り文字の変更

read コマンドで行を読み込む際に IFS の値を一時的に変更することで、区切り文字を変更することができます。

以下のようなカンマ区切りのテキスト(sample.csv)がある場合、各行の内容をカンマで区切って読み込んで変数に格納することができます。

sample.csv
1st,2nd,3rd
abc,def,ghi
jkl,mno,pqr
123,456,789
012,345,678
901,234,567

以下は各行を読み込む際にカンマで区切って3つの変数(cs1 cs2 cs3)に格納して、2列め(cs2)だけの値を出力する例です。

この例の場合、各行がカンマで3つに区切られるので変数を3つ指定しています。

また、区切り文字を一時的にカンマ(,)変更するため、read コマンドの前に「IFS=,」を指定しています。

whileTest5_1_2
#! /bin/bash
while IFS=, read cs1 cs2 cs3
do
  echo "$cs2"
done < ./sample.csv

以下は実行例です。

./whileTest5_1_2    return
2nd
def
mno
456
345
234

パイプ

以下はパイプを使って sample.txt を読み込んで1行ずつ表示するスクリプトです。

cat コマンドでファイルを読み込みその出力をパイプで while に渡しています。

whileTest5_2
#! /bin/bash
cat ./sample.txt | while read line   #cat コマンドの出力をパイプで渡す
do
  echo "$line"
done

以下は for 文を使ってカレントディレクトリのテキストファイルを1つずつ取得して、ファイル名を表示し、read コマンドif 文でユーザからの入力が y であれば、パイプを使ってそのファイル名を while 文に渡してファイルの内容を出力するスクリプトです。

whileTest5_2_1
#! /bin/bash
for file in *.txt   #カレントディレクトリのテキストファイルを順に処理
do 
  echo "File Name: $file"   #ファイル名を表示
  read -p "show content? y/n > " str   #ユーザー入力を変数 str に格納
  if [ "$str" = y ]   #ユーザー入力が y であれば(それ以外であれば何もしない)
  then 
    cat "$file"| while read line #cat でファイルを読み込みパイプで while に渡す
    do 
      echo "$line"
    done    # while 文はここまで
  fi    # if 文はここまで
done  # for 文はここまで

以下は実行例です。

./whileTest5_2_1    return
File Name: log.txt      # File Name: ファイル名 を表示
show content? y/n >     return #何も入力せず return
File Name: sample.txt
show content? y/n > y    return # y を入力して return
First Line       # ファイルの内容が表示される
Second Line
Third Line

Fifth Line (Fourth Line is empty)
Sixth Line n
File Name: sample2.txt
show content? y/n > n    return # n を入力して return

ヒアドキュメント

コマンド置換 ` ` を使って cat コマンドで読み込んだファイルの内容を変数に格納し、その変数をヒアドキュメントで読み込ませることもできます。

whileTest5_3
#! /bin/bash
file=`cat ./sample.txt`   #cat コマンドの出力をコマンド置換で変数へ代入
while read line
do
  echo "$line"
done << EOF  #cat ヒアドキュメントで変数を読み込む
$file
EOF

ヒアドキュメント

ヒアドキュメントは bash の機能(リダイレクト)の1つで、複数行の文字列を記述できる機能です。

以下は man bash からの抜粋です。

Here Documents
This  type  of  redirection  instructs the shell to read input from the
current source until a line containing  only  word  (with  no  trailing
blanks)  is seen.  All of the lines read up to that point are then used
as the standard input for a command.

The format of here-documents is:

<<[-]word
   here-document
delimiter

例えば、スクリプトの中で複数行の出力をするには echo コマンドを複数回記述することもできますが、ヒアドキュメントを使うと簡潔に記述することができます。

以下は echo コマンドとヒアドキュメント(と cat コマンド)を使って3行の文字列を出力する例です。

#! /bin/bash

echo abc     # echo で出力
echo def     # echo で出力
echo ghi     # echo で出力

cat << EOT     # cat とヒアドキュメントで出力
abc
def
ghi
EOT

##### 以下は実行結果 ####
abc
def
ghi
abc
def
ghi

ヒアドキュメントとして入力した文字列は標準入力として扱われるので、cat コマンドtr コマンドなどと使うことができます。(cat コマンドはファイルが指定されていない場合は標準入力から読み込みます)

echo コマンドは標準入力から読み込まないので使えません。

以下が書式です。

<< IDとなる文字列
   出力文字列
IDとなる文字列

「IDとなる文字列」は同じ値を指定します。 EOT や EOD などがよく使われますが、任意の文字を使うことができます。

最初(開始位置の)「IDとなる文字列」の前後にはスペースがあってもなくても構いません。

但し、終了の「IDとなる文字列」の前後に空白(スペースなど)があると正しく機能しません。

ヒアドキュメントに記述した変数は(引用符で括っても)展開され、コマンド置換も実行されます。

#! /bin/bash
foo=12345   #変数 foo を定義
bar=abcde   #変数 bar を定義

cat << EOD   #ヒアドキュメント開始
here document test
$foo
foo is ${foo}
foo is "$foo"
foo is '$foo'
$bar
$(date)
`date`
EOD

以下が実行結果です。

here document test
12345   #変数 foo
foo is 12345   #変数 foo
foo is "12345"   #変数 foo
foo is '12345'   #変数 foo
abcde   #変数 bar
2019年 10月 6日 日曜日 13時53分41秒 JST   #コマンド置換 $(date)
2019年 10月 6日 日曜日 13時53分41秒 JST   #コマンド置換 `date`

tr コマンドを使った例

以下は cat コマンドではなく tr コマンドを使って小文字を大文字に変換する例です。

#! /bin/bash
foo=12345   #変数 foo を定義
bar=abcde   #変数 bar を定義

tr a-z A-Z << ET    #ヒアドキュメント開始(tr コマンドを使用)
here document test
${bar}
$PATH
ET

以下が実行結果です。

HERE DOCUMENT TEST   #tr コマンドで大文字に変換
ABCDE   #変数 bar(tr コマンドで大文字に変換)
/USR/LOCAL/BIN:/USR/BIN:/BIN:/USR/SBIN:/SBIN:/USERS/FOO/BIN   #変数 PATH

変数やコマンドを展開しない

「IDとなる文字列」を引用符(ダブルクォートまたはシングルクォート)で囲むと変数やコマンドを展開しません。

#! /bin/bash
foo=12345   #変数 foo を定義
bar=abcde   #変数 bar を定義

cat <<"EDT"   #ヒアドキュメント開始(IDとなる文字列を引用符で括る)
$foo
${bar}
$(date)
EDT

以下が実行結果です。

$foo   #変数は展開されない
${bar}   #変数は展開されない
$(date)   #コマンドは展開されない

行頭のタブを無視

<<- のようにハイフン(-)を指定すると、行頭のタブ(スペースは対象外)が無視されるようになりスクリプトのインデントを崩さずにヒアドキュメントを記述できます。

#! /bin/bash
cat <<- ECD   #ヒアドキュメント開始
while : 
	do 
		this
	done
ECD

以下が実行結果です。

while : 
do        #行頭のタブが無視(削除)される
this      #行頭のタブが無視(削除)される
done      #行頭のタブが無視(削除)される

ヒアドキュメントの内容を変数に代入

ヒアドキュメントの内容を変数に代入するには、以下のようにコマンド置換 $( ) を使います。

変数の値を出力する際は、ダブルクォートで囲まないと1行で表示されます。

#! /bin/bash
foo=12345   #変数 foo を定義
bar=abcde   #変数 bar を定義

heredoc=$(cat << EOHD    #変数 heredoc に内容を代入
abc
def
$foo
$bar
`date`
EOHD)

echo $heredoc    #変数 heredoc を出力
echo "$heredoc"    #変数 heredoc をダブルクォートで括って出力

以下は実行結果です。

abc def 12345 abcde 2019年 10月 6日 日曜日 14時25分02秒 JST    #1行で表示
# ダブルクォートで囲むと改行が反映される
abc
def
12345
abcde
2019年 10月 6日 日曜日 14時25分02秒 JST

コマンドライン(ファイルの作成)

コマンドラインでも使うことができます。

tr a-z A-Z << EOD   return 
> abcdefg   return 
> hijklmn   return 
> EOD   return 
ABCDEFG
HIJKLMN

foo=$(cat << EOT   return 
> apple   return 
> grape   return 
> orange   return 
> EOT)   return 

echo $foo   return 
apple grape orange

echo "$foo"   return #変数に代入した場合はダブルクォートで括ると改行が反映される
apple
grape
orange

以下のようにリダイレクトと組み合わせることでヒアドキュメントの内容のファイルを作成することができます。

リダイレクト > は指定したファイルが存在しなければ新規作成、存在すれば上書きになります。

追記は >> を使います。

cat << EOF > sample.csv  return # sample.csv を新規作成(または上書き)
> abc,def,ghi  return
> jkl,mno,pqr  return
> 123,456,789  return
> EOF  return

cat sample.csv   return # sample.csv の内容を確認
abc,def,ghi
jkl,mno,pqr
123,456,789

cat << EOF >> sample.csv    return # sample.csv に追記
> 012,345,678  return
> 901,234,567  return
> EOF  return

cat sample.csv    return # sample.csv の内容を確認
abc,def,ghi
jkl,mno,pqr
123,456,789
012,345,678
901,234,567

一時ファイルの作成 mktemp

mktemp コマンドを使うと、作業用に一時ファイルやディレクトリを作成することができます。

作成される一時ファイルやディレクトリの名前は、自動的に他と異なるユニークなものに設定されます。

mktemp [オプション] [文字列]

以下のようなオプションがあります。

オプション 意味
-d 一時ファイルではなく一時ディレクトリを作成
-q エラー・メッセージを表示しない
-t 接頭辞 作成されるファイル名に指定した接頭辞を付ける
文字列 大文字「X」を末尾に含んだ文字列を指定(Xはランダムな文字に置き換えられる)

mktemp コマンドを実行すると、空のファイルが作成され標準出力にファイルパスが表示(出力)されます。

デフォルトでは環境変数 TMPDIR に設定されているディレクトリ(一時ファイル用フォルダ)に、tmp.XXXXXXXX(Xはランダムな文字)というような名前でファイルが作成されます。

一時ファイル用フォルダに保存されたファイルは一定時間が経過すると削除されます。

mktemp    return  #一時ファイルを作成
/var/folders/xxx/xxxx...0000gn/T/tmp.9C98xs2h  #作成されたファイルのパス

以下のようにコマンド置換 $( ) や ` ` を使ってファイルパスを変数に保存すれば、変数を使ってファイルに書き込みをしたり読み出したりすることができます。

作成したファイルのアクセス権は 600 (rw-------)になります(ディレクトリは 700/rwx------)。

tmpfile=`mktemp`   return  #一時ファイルを作成
# または tmpfile=$(mktemp)

echo $tmpfile    return  #ファイルの場所(パス)を確認
/var/folders/xxx/xxxx...0000gn/T/tmp.irCNqxFo

ls -l $tmpfile    return  #ファイルの詳細を確認
-rw-------  1 foo  staff  0  9 27 10:13 /var/folders/xxx/xxxx...0000gn/T/tmp.irCNqxFo

rm $tmpfile    return  #ファイルを削除

echo $TMPDIR    return  #環境変数 TMPDIR の値
/var/folders/xxx/xxxx...0000gn/T/

以下はオプションの文字列で保存先のディレクトリを指定して tmpDir という作成したディレクトリに一時ファイルを作成する例です。

mkdir tmpDir    return  #保存先のディレクトリを作成
            
tmpfile=`mktemp ./tmpDir/test.XXXXXX`   return  #./tmpDir/test.XXXXXX を指定してファイルを作成

echo $tmpfile    return  #ファイルパスを確認
./tmpDir/test.RhWvxh

rm $tmpfile     return  #ファイルを削除

以下は -t オプションを指定してファイル名に接頭辞を付けて作成する例です。

tmpfile=`mktemp -t test`    return  #-t を指定してファイルを作成
            
echo $tmpfile     return  #ファイルパスを確認
/var/folders/xxx/xxxx...0000gn/T/test.RC39yNnN

rm $tmpfile     return  #ファイルを削除

以下はカレントディレクトリ直下の tmpDir ディレクトリに一時ファイルを作成し、 時刻と作成したファイルの情報、 ディレクトリの一覧を書き込み表示するスクリプト myscript4 の例です。

catコマンドで内容を表示後、作成した一時ファイルを削除し、確認のためディレクトリの一覧を出力するようにしています。

myscript4
#!/bin/bash
tmpfile=`mktemp ./tmpDir/sample.XXXXXXXXXX`  #一時ファイルを作成
date > $tmpfile  #日付時刻をファイルに出力
ls -l $tmpfile >> $tmpfile  #作成したファイルの情報を追記
echo "tmpDir list: `ls ./tmpDir`" >> $tmpfile  #tmpDir ディレクトリの一覧を追記
cat $tmpfile  #一時ファイルの内容を表示
rm $tmpfile  #一時ファイルを削除
echo "current tmpDir list: `ls ./tmpDir`"  #tmpDir ディレクトリの一覧を表示

以下は上記ファイル myscript4 の実行結果です。

bash myscript4   return  #スクリプトを実行
2019年 9月27日 金曜日 14時17分36秒 JST  #date 
-rw-------  1 foo staff  48  9 27 14:17 ./tmpDir/sample.HGkqUGMgUB  #ls -l(一時ファイルが表示される)
tmpDir list: sample.HGkqUGMgUB  #ls ./tmpDir
current tmpDir list:   #ファイルは削除されているので表示されない

スクリプト終了時に削除

前述の例では、スクリプトの最後の方に一時ファイルを削除するコマンドを記述しましたが、何らかの理由で処理の途中で終了してしまった場合はファイルを削除できない可能性があります。

trap コマンドを使うと、シェルスクリプト終了時にファイルを削除することができます。

trap コマンドでは、シグナルを受け取った際に実行するコマンドを設定することができます。

trap "実行するコマンド" シグナル

シェルスクリプト終了時に必ず実行される処理を指定するには、EXIT シグナル(0番のシグナル)を trap(捕捉)します。

trap "実行するコマンド" EXIT
# または trap "実行するコマンド" 0

以下は trap コマンドを使ってスクリプトの終了時に作成した一時ファイルを削除するように myscript4 を書き換えた例です。

以下のスクリプトの trap コマンドは、終了のシグナルを捕捉してファイルを削除するのでスクリプト中では削除は確認できません。

また、rm コマンドでは -f オプションを指定してメッセージを表示せずに削除するようにしています。ディレクトリを中身ごと削除する場合は -r も併せて指定します。

myscript4(更新版)
#!/bin/bash
tmpfile=`mktemp ./tmpDir/sample.XXXXXXXXXX`
trap "rm -f $tmpfile" EXIT  #終了のシグナルを捕捉してファイルを削除
date > $tmpfile
ls -l $tmpfile >> $tmpfile
echo "tmpDir list: `ls ./tmpDir`" >> $tmpfile
cat $tmpfile

以下は実行例です。

bash myscript4   return  #スクリプトを実行
2019年 9月27日 金曜日 15時27分17秒 JST
-rw-------  1 foo  staff  48  9 27 15:27 ./tmpDir/sample.SuRdtDwLdg
tmpDir list: sample.SuRdtDwLdg

ls tmpDir/   return  #削除されているかを確認
#何も表示されず削除されている

シェルの展開順序

コマンドラインやシェルスクリプトでは、以下のいずれも全く同じことになります。

echo abc    return
abc

echo 'abc'    return
abc

echo "abc"    return
abc

シェルはユーザが入力した内容を受け取ってコマンドとして実行します。

その際、ユーザが入力した内容はコマンドとして実行する前にシェルにより解釈され、必要に応じて整形(変換)されます。

言い換えると、シェルは受け取った文字列をそのままコマンドとして実行するのではなく,まずシェル自身が入力された文字列を解釈し,特定のルールに従って変換してからコマンドを実行します。

上記の echo コマンドの場合、引数の文字列は空白やメタキャラクタを含まない単なる文字列なので、引数の引用符が取り除かれ(quote removal)て「echo abc」に変換されて実行されるので、全て同じことになります。(下記参照

シェルには展開(Expansion)という機能があり、それらは特定の順序で実行されます。

man bash で「Expansion」を見ると以下のように記述されています。

EXPANSION
   Expansion is performed on the command line after it has been split into
   words.  There are seven kinds of expansion performed: brace  expansion,
   tilde  expansion,  parameter  and variable expansion, command substitu-
   tion, arithmetic expansion, word splitting, and pathname expansion.

   The order of expansions is: brace expansion, tilde  expansion,  parame-
   ter,  variable  and arithmetic expansion and command substitution (done
   in a left-to-right fashion), word splitting, and pathname expansion.

bash の場合、シェルの展開順序は以下のようになっているようです。

  1. brace expansion(ブレース展開)
  2. tilde expansion(チルダ展開)
  3. parameter and variable expansion/arithmetic expansion (パラメータ・変数展開/算術式展開)
  4. command substitution(コマンド置換→3と同じステップ?)
  5. word splitting (単語分割)
  6. pathname expansion(パス名展開)

最後に必要であれば quote removal(クォートの削除)が行われコマンドに引数が渡されて実行されます。

Expansion 概要 記述例
brace expansion
ブレース展開
波括弧 { } の中に文字列をカンマで区切って指定すると、シェルがそれらの文字列を展開する x{a,b,c} {a..g}
tilde expansion
チルダ展開
チルダ「~」を前後の文字列によりホームディレクトリ や PWD の値などに展開 ~/.bashrc ~/foo
parameter expansion
パラメータ展開
変数の値の一部分を取り出したり置換する ${LANG:0:5} ${PATH##*:}
variable expansion
変数展開
変数の展開 $foo $LANG ${foo}
arithmetic expansion
算術式展開
算術式の結果を展開する $((算術式))
command substitution
コマンド置換
コマンド名をコマンドの出力で置き換える $( ) ` `
word splitting
単語分割
引用符の付いていない文字列を空白など(IFSで設定)により単語に分割
pathname expansion
パス名展開
*、?、[文字] が含まれ場合「パターン」とみなしマッチするファイルがあれば、そのファイル名に置き換える *、?、[文字]

前述の例では引数を引用符で括った場合とそうでない場合で同じ動作でしたが、以下の場合は異なります。

引数「*」を引用符で括らない場合、シェルが勝手に解釈して「*」を展開(パス名展開)してしまうため、find コマンドにアスタリスクが渡らないためエラーになります。(下記参照

find . -name *    return # エラー
find: 1: unknown primary or operator

find . -name '*'    return # 正しく作動
.
./test.csv
./log.txt
./sample.txt

find . -name "*"    return # 正しく作動
.
./test.csv
./log.txt
./sample.txt

bash の以下の書式(オプション)を使うと、シェルが変数やパス名をどのように展開して動作しているかを確認することができます。

bash -x -c "コマンド"

例えば、カレントディレクトリを表す環境変数 $PWD を echo コマンドで出力する場合、以下のように記述すると変数 $PWD が展開されて値が表示されます。

echo $PWD    return 
/Users/foo   #カレントディレクトリが表示される

上記の書式で実行したコマンドを確認すると以下のようになります。

+ の表示のある行にはシェルにより展開されたコマンドが表示されています。

この場合、変数 PWD を展開しているのは echo コマンドではなく、シェルが行っています。

echo コマンドはシェルから文字列「/Users/foo」を渡されて表示しています。

bash -x -c "echo $PWD"   return
+ echo /Users/foo   #シェル(bash)により変数が展開されて echo コマンドに渡されている
/Users/foo

以下は、変数を代入する際に値に空白があるのに引用符で括らないのでエラーになる例です。

foo=ab  c   return
-bash: c: command not found

bash -x -c "foo=ab  c"   return #上記コマンドの動作を確認
+ foo=ab  #引数には間に空白があるのでシェルにより ab と c に分割され、foo=ab を実行しようとする
+ c     # c というコマンドを実行しようとする
bash: c: command not found     # c というコマンドは見つからない
echo $?     return # コマンドの終了ステータスを確認
127 # エラー(0意外なので失敗)

以下は変数 foo に空白を含む文字列を設定して、変数 foo をダブルクォートで括って出力する場合とそうでない場合の動作を確認する例です。

foo="a b c    d"  return # 変数 foo に空白を含む文字列を設定

echo $foo  return
a b c d   # 空白が取り除かれて出力される

bash -x -c "echo $foo"  return # 上記コマンドの動作を確認
+ echo a b c d   # シェルにより空白が取り除かれて echo に渡されている
a b c d


echo "$foo"  return
a b c    d   # 空白はキープされる

bash -x -c "echo \"$foo\""  return # 上記コマンドの動作を確認
+ echo 'a b c    d'  # シェルにより変数が展開されシングルクォートで括られて(空白は削除されず)echo に渡されている
a b c    d

echo a b c   d return
a b c d   # 空白が取り除かれて出力される

bash -x -c "echo a b c   d"  return # 上記コマンドの動作を確認
+ echo a b c d  # シェルにより空白が取り除かれて echo に渡されている
a b c d

最初の例の echo abc の動作は以下のようになり、全て同じになっています。

bash -x -c "echo abc"  return
+ echo abc # シェルから echo にそのまま渡される
abc

bash -x -c "echo 'abc'"  return
+ echo abc # シェルによりシングルクォートが取り除かれて echo に渡される
abc

bash -x -c "echo \"abc\""  return
+ echo abc # シェルによりシダブルクォートが取り除かれて echo に渡される
abc

また、先述の「find . -name *」のように「*」を引用符で括らないとエラーになる動作は以下のようになっています。

以下は、カレントディレクトリに「log.txt」「sample.txt」「test.csv」の3つのファイルがある例です。

bash -x -c "find . -name *"  return
+ find . -name log.txt sample.txt test.csv  # シェルにより * が展開され3つのファイル名が find に渡される
find: sample.txt: unknown primary or operator   # 空白区切りの値が渡されてエラー

bash -x -c "find . -name '*'"  return
+ find . -name '*'  # シェルにより * が find に渡され想定通りに動作する
.
./test.csv
./log.txt
./sample.txt