ログイン

BashのTips

Top / bash / tips

Japanese / English

Bash、およびその周辺コマンドに関するTips。

標準入出力

各入出力の番号(ファイル・ディスクリプター)

  • 0: 標準入力(<)
  • 1: 標準出力(>)
  • 2: 標準エラー出力

入力のリダイレクト

[番号/0]<ファイル名
  • ファイルをオープンし、指定した番号で読み込めるようにする。
  • 番号を省略すると標準入力からリダイレクトする。

出力のリダイレクト

[番号/1]>ファイル名    <-上書き
[番号/1]>>ファイル名   <-追記
  • ファイルをオープンし、指定した番号で書き込めるようにする。
  • 番号を省略すると標準出力からリダイレクトする。
  • 注意:>を用いた場合、ファイルが存在するとファイルサイズは0になる。

ファイル・ディスクリプターの複製

[p]>&q

TODO: 分かりやすい説明

Tips: 標準出力を画面とファイルに同時出力

コマンド | tee ファイル名
  • teeは、標準入力を標準出力、およびファイル(複数も可)に同時出力するコマンド。
  • $ ./a.out | tee log
    

Tips: 標準出力と標準エラー出力を同一ファイルに追記

コマンド >> log 2>&1
  • 例1
    $ ./a.out >> log 2>&1
    
  • 例2:表示しながら出力
    $ ./a.out 2>&1 | tee log
    

Tips: 標準出力と標準エラー出力を別ファイルに出力

コマンド 1> 標準出力ファイル名 2> 標準エラー出力ファイル名
  • ./a.out 1> file1 2> file2
    

Tips: 標準エラー出力へ画面表示

echo "this is error" >&2

変数

Bashでは値を保持するものを「パラメータ」、その中で名前のついているものを「変数」と呼んでいる。

値の代入と参照

  • 代入
    変数名=[値]
    
    • 注意:=の両側に空白を入れてはいけません!
    • 値なしの場合は空文字列が代入される。
  • 参照
    $変数名
    又は
    ${変数名}
    
    • 変数は特に宣言をしなくても参照することができる。
    • {}は基本的にあってもなくても構わないが、どこまでが変数か明確にしたい場合は{}をつける。

変数の開放

unset 変数名

特殊パラメータ

パラメータ名 意味
$0 シェル又はシェルスクリプトの名前。
$数値 シェルの引数(数値は1以上)。2桁以上の数値を指定する場合は{}で囲む必要がある。
$* $1 $2 ...(全て)。
$@ 同上。
$# $1, $2, の個数。
$$ プロセスID。
$? 最後に実行したフォアグラウンドコマンドの終了ステータス。0なら正常終了。
$! 最後に実行したバックグラウンドコマンドの終了ステータス。
$- 現在のbashのオプションフラグ。setコマンドで変更可能。

Tips: テンポラリファイル名

FILE=temp.$$

プロセスIDは一意に決まるので、重複しないファイル名を生成することができる。

特殊変数

  • 自動設定
    変数名 意味
    OLDPWD 1つ前の作業ディレクトリ。
    PPID シェルの親のプロセスID。読み込み専用。
    PWD 現在の作業ディレクトリ。
    RANDOM 0-32767までのランダム整数。RANDOMに値を代入すると初期化される。
    HOSTNAME 現在のホスト名。
    SECONDS シェルが起動されてからの秒数。
  • ユーザ指定可能
    変数名 意味
    IFS 区切り文字。デフォルトは「スペース+タブ+改行」
    PS1 プロンプト。デフォルトは "[\u@\h \W]\\$ "

変数の展開

変数の値が設定されていないか空文字の場合、文字列に置換。

${変数名:-文字列}
  • 注:コロンを忘れないように!

変数の値が設定されていないか空文字の場合、文字列に置換して変数に代入。

${変数名:=文字列}

変数の値が設定されていないか空文字の場合、エラーを表示して強制終了。

${変数名:?}                  // デフォルトのエラーメッセージ
${変数名:?エラーメッセージ}  // エラーメッセージを設定

変数の値が設定されている場合、指定した文字列に置換(変数に代入はしない)。

${変数名:+文字列}

変数の値の一部を取り出す。開始位置は0以上。長さを省略すると最後まで。

${変数名:開始位置:[長さ]}
  • 変数が配列(後述)で変数名の末尾に[@]をつけると、部分配列を取り出すことができる。

文字数。

${#変数名}

変数の先頭がマッチした場合、最短マッチ部分を削除

${変数名#パターン}

変数の先頭がマッチした場合、最長マッチ部分を削除

${変数名##パターン}

変数の末尾がマッチした場合、最短マッチ部分を削除

${変数名%パターン}

変数の末尾がマッチした場合、最長マッチ部分を削除

${変数名%%パターン}

パターンとして使える文字

  • 一般的な正規表現は使えないので注意。
    * 任意の文字列
    ? 任意の一文字
    [...] カッコ内のいずれか
  • $ TEMP=123abc123xyz
    $ echo ${TEMP#??}
    3abc123xyz
    $ echo ${TEMP#*}
    123abc123xyz
    $ echo ${TEMP##*}
     
    $ echo ${TEMP#1*3}
    abc123xyz
    $ echo ${TEMP##1*3}
    xyz
    

間接展開:変数の値を変数名として展開

${!変数名}
  • $ A=BBB
    $ BBB=1
    $ echo ${A}
    BBB
    $ echo ${!A}
    1
    

Tips: 拡張子の変更

PNG=abc.png
GIF=${PNG%.png}.gif

Tips: パスからファイル名のみを取り出す

$ FILE=/home/hoge/test.dat
$ echo ${FILE##*/}
test.dat
  • basenameというコマンドを使う方法もある。
    $ basename /home/hoge/test.dat
    test.dat
    

Tips: パスからディレクトリ名のみを取り出す

FILE=/home/hoge/test.dat
DIR=${FILE%/*}

Tips: パスから拡張子のみを取り出す

FILE=abc.png
EXT=${FILE##*.}

Tips: 文字列の末尾の文字を取り出す

$ NUM=12345
$ echo ${NUM:${#NUM}-1:1}
5

Tips: パスが相対パスなら絶対パスへ変換

[ "${DIR:0:1}" != "/" ] && DIR=$(pwd)/${DIR}

配列

個々の要素への値の代入

配列名[要素番号]=[値]
  • 注意:=の両側に空白を入れてはいけません!
  • 特に宣言をしなくても配列に代入することができます。要素番号は0以上で指定する。
  • A[0]="abc"
    A[1]="ABC"
    A[2]=-100
    

個々の要素の値の参照

${配列名[要素番号]}
  • 変数と同様、特に宣言をしなくても配列を参照することができる。要素番号は0以上で指定する。
  • echo ${A[2]}
    echo ${A[1]}
    echo ${A[0]}
    

一括代入

配列名=( 要素1 要素2 ... )
  • 配列に値を一気に代入する。空白が配列の切れ目になる。
  • 例1
    array=( Jan Feb Mar Apr )
    echo ${array[2]}
    
  • 例2: 現在のディレクトリの内容を配列に格納
    LS_RESULT=( $(ls) )
    
    • この例は後述のforループで用いると有用。
  • 例3: 配列を空にする
    array=( )
    

一括参照

${配列名[@]}
又は
${配列名[*]}
  • 厳密には上の二つの意味は異なる。
    • ${配列名[@]}: "${配列名[0]}" "${配列名[1]}" "${配列名[2]}"...
    • ${配列名[*]}: "${配列名[0]}<SEP>${配列名[1]}<SEP>${配列名[2]}..."
      • <SEP>は特殊変数IFSの第1文字目。
  • 例1
    array=( Jan Feb Mar Apr )
    echo ${array[@]} 
    
  • 例2: 現在のディレクトリの内容を配列に格納して表示
    LS_RESULT=( $(ls) )
    for FILE in ${LS_RESULT[@]} ; do
        echo ${FILE}
    done
    

配列の要素数の参照

${#配列名[@]}
  • 例1
    A=( DJF JJA MAM SON )
    echo ${#A[@]}
    
  • 例2: 配列の中身を表示(for 〜 in 〜 を使わない例)
    A=( DJF JJA MAM SON )
    for(( i=0; ${i}<=${#A[@]}-1; i=${i}+1 )) ; do
        echo ${A[$i]}
    done
    

Tips: 配列の末尾へ要素を追加する。

  • その1
    A=( DJF JJA MAM )
    A=( ${A[@]} SON )
    
  • その2(こっちの方が高速かも)
    A=( DJF JJA MAM )
    A[${#A[@]}]=SON
    

Tips: 部分配列を取り出す

B=( "${A[@]:2:3}" )
  • この場合、B[0]=A[2], B[1]=A[3], B[2]=A[4]になる。

Tips: 2つの配列、双方に含まれる要素を抜き出した配列を作成する。

IFS=$'\n'      // 配列の区切り文字を改行に変更
配列名=( $( { echo "${配列名1[*]}" ; echo "${配列名2[*]}" } | sort | uniq -d ) )

Tips: 2つの配列、双方に含まれる要素以外を抜き出した配列を作成する。

IFS=$'\n'      // 配列の区切り文字を改行に変更
配列名=( $( { echo "${配列名1[*]}" ; echo "${配列名2[*]}" } | sort | uniq -u ) )

連想配列

  • Bash 4以降のみ利用可能

個々の要素への値の代入

連想配列名["key名"]=[値]
  • A["Tree"]="Ki"
    A["Forest"]="Mori"
    

個々の要素の値の参照

${連想配列名["key名"]}
  • echo ${A["Tree"]}
    echo ${A["Forest"]}
    

一括代入

連想配列名=( ["key-1名"]=値-1 ["key-2名"]=値-2 ... )

展開

展開の優先度は以下の通り。

  • ブレース展開
  • チルダ展開
  • パラメータ・変数・算術式展開
  • コマンド置換 (左から右へ)
  • 単語分割
  • パス名展開。

ブレース展開

任意の文字列を生成する展開。{ } 内でコンマで区切って文字列を指定すると、それら全ての組み合わせで文字列を生成する。右から展開される。入れ子も可能。以下の例のように文字列の一部が共通の場合に威力を発揮する。

  • 例1
    $ rm test{A,BB,CCC}
    $ rm testA testBB testCCC  // 上と同じ意味
    
  • 例2
    $ ls /home/hoge/{bin,lib,exe}
    $ ls /home/hoge/bin /home/hoge/lib /home/hoge/exe  // 上と同じ意味
    
  • 例3
    $ echo a{a,b,{c,cc,ccc}}
    aa ab ac acc accc
    

規則性のある文字列は、..を利用することも可能。

  • 例4
    $ echo {1..5}
    1 2 3 4 5
    $ echo {5..1}
    5 4 3 2 1
    $ echo {001..005}
    001 002 003 004 005
    $ echo {a..z}
    a b c d e f g h i j k l m n o p q r s t u v w x y z
    $ echo {00..23}:{0..5}{0..9}
    00:00 00:01 00:02 00:03 00:04 00:05 00:06 00:07
    ...(中略)...
    23:52 23:53 23:54 23:55 23:56 23:57 23:58 23:59
    

チルダ展開

チルダで始まる単語は、チルダ展開の候補となる。チルダ展開文法を含む単語には、チルダ展開以外の文字列を含んではいけない。

現在ログインしているユーザのホームディレクトリ($HOME)に置換

~
~/

指定したログイン名のユーザのホームディレクトリに置換

~ログイン名
~ログイン名/
  • 該当するログイン名がない場合は置換は実施されない。

現在のワーキングディレクトリ(PWD)に置換

~+
~+/

一つ前のワーキングディレクトリ(OLDPWD)に置換

~-
~-/

パラメータ展開

変数の展開を参照。

条件分岐

if文

if 処理A ; then
  処理1
elif 処理B ; then
  処理2
else
  処理0
fi
  • 「elif〜処理2」と「else〜処理0」は省略可能。
  • 「elif〜処理2」は何度でも繰り返し可能。
  • 処理Aの終了ステータスが0の場合、処理1が実行される。
  • 処理Bの終了ステータスが0の場合、処理2が実行される。
  • どのif,elifでも処理が実行されない場合、処理0が実行される。
  • A="ABC"
    if [ "${A}" = "abc" ] ; then
      echo "if ok"
    elif [ "${A}" = "ABC" ]
      echo "elif ok"
    else
      echo "else ok"
    fi
    
    • これを実行すると"elif ok"と表示される。
    • ちなみに [ はれっきとしたコマンド。

case文

case 変数 in
パターン1)
  処理1
  ;;
パターン2)
  処理2
  ;;
 *)
  デフォルト処理
  ;;
esac

Tips: 複雑なand/or条件

  • [] の外側で && や || を使うことで複雑な表現が可能。
    if [ ${AAA} -eq 1 -o ${AAA} -eq 2 ] && [ ${BBB} -eq 1 ]; then
      ...
    fi
    

繰り返し

for文(1)

for 変数名 in 値1 値2 ... ; do
  処理
done
  • 変数に値1, 値2... が順番に展開されながら処理を繰り返す。

for文(2)

for(( 算術式1; 算術式2; 算術式3 )) ; do
  処理
done
  • 算術式1: 最初に評価される。
  • 算術式2: 繰り返しの度に評価される。これが真の間繰り返す。
  • 算術式3: 繰り返しの度に最後に評価される。
  • 算術式で使える演算子は「演算」の項を参照。

while

while 処理A ; do
  処理1
done
  • 処理Aが終了ステータス0を返しつづける間、処理1を繰り返し実行する。

ループ途中からループを脱出する

break [深さ]
  • for, while, until, select ループから脱出します。
  • 深さは脱出したいループの深さ。省略時は1、つまり一番内側のループから抜ける。

ループ途中からループ先頭に戻る

continue [深さ]
  • for, while, until, select ループの先頭に戻る。
  • 深さは先頭に戻りたいループの深さ。省略時は1、つまり一番内側のループの先頭に戻る。

Tips: for i in 〜 に渡せる連番を生成

seq 1 30
seq -w 1 30     // 同じ桁数になるように0を埋める
echo ${01..30}  // ブレース展開を利用
  • for i in $( seq -w 1 30 ) ; do
      echo $i
    done
    
    • for(( i=1; i<=30; i=$i+1 )) では0を埋めることができない。

Tips: 無限ループ

while :
do
  (処理)
done
  • コロン(:)はヌルコマンド。これは常にtrueを返す。

日付処理(date)

Tips: 特定の年月日を整形して出力

YMD="2001-03-05"
date --date "${YMD}" +%Y%m%d

Tips: 特定の年の年月日を整形して出力

YEAR=2004
for(( d=0 ; $d<=365; d=$d+1 )) ; do
    date --date "${YEAR}-01-01 +${d}days" +%Y%m%d
done

Tips: 指定した年に含まれる日数を出力

YEAR=2000
date --date "${YEAR}-01-01 +1year-1days" +%j

Tips: 指定した年月に含まれる日数を出力

YEAR=2000
MONTH=2
date --date "${YEAR}-${MONTH}-01 +1month-1days" +%d

演算(let, expr, bc 等)

bash算術式(優先度順)

  • 整数のみ利用可能。
  • man bashARITHMETIC EVALUATION の項を参照のこと。
    演算子 意味
    v++, v-- 後置インクリメント, 後置デクリメント(つまり参照してから1を足す or 引く)
    ++v, --v 前置インクリメント, 前置デクリメント(つまり1を足した or 引いた後参照)
    -, + 単項-演算, 単項+演算(つまり単なる符号)
    !, ~ 論理否定, ビット毎の否定
    ** べき乗
    *, /, % 乗法, 除法, 剰余
    +, - 加算, 減算
    <<, >> 左ビットシフト, 右ビットシフト
    <=, >=, <, > 比較
    ==, != 比較(等価, 不等価)
    & ビット毎のAND演算
    ^ ビット毎の排他的OR演算
    ビット毎のOR演算
    && 論理積
    || 論理和
    expr?expr:expr 条件付き演算
    =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |= 代入
    , |列挙

利用例

  • 算術式の評価: let 算術式 又は ((算術式))
  • 算術式の評価+参照: $((算術式))

Tips: 変数に1を加える

変数名++    <-参照してから1を加える
++変数名    <-先に変数に1を加えてから参照する
  • $ A=5
    $ echo $((A++))
    5
    $ echo $A
    6
    $ A=5
    $ echo $((++A))
    6
    $ echo $A
    6
    

let, expr, bc の使い分け

  • let: bash算術式の評価(整数のみ)、bashの組み込みコマンド
  • expr: 整数のみ
  • bc: 小数もOK、複雑な式に対応

let

let (算術式)
((算術式))      <-上と等価の別表現
  • 算術式は上を参照
  • 変数名に$はつけない!
  • $ A=2
    $ let A++
    

expr

expr (値1) (演算子) (値2)
  • 値1, 値2は整数である必要がある。
  • 演算子は「演算子一覧」を参照。
  • 条件式の場合、真ならば1、偽ならば0を返す。

Tips: 桁数を指定した計算

echo "scale=5; 40000/3000" | bc

Tips: 計算結果を指数表記で表示

echo "scale=20; 1/30000" | bc | xargs printf "%.5e\n" 

Tips: 小数同士の比較

if [ $( echo "${VALUE} >= 0" | bc ) -eq 1 ] ; then
  ...(VALUE>=0の時の処理)...
else
  ...(VALUE<0の時の処理)...
fi

Tips: 常用対数の計算

echo "scale=5; l(12345)/l(10)" | bc -l

ls

Tips: ディレクトリ名を指定したときディレクトリ自体を表示する

ls -d

diff

Tips: 空白の違いを無視する

diff -EwB file-1 file-2

Tips: ディレクトリごと中身を比較する

diff dir-1 fir-2

テキスト表示(cat/tac)

catとtac

catはそのまま表示、tacは逆順に表示。

cat ファイル名
tac ファイル名

テキストフィルタ(cut/paste/awk/printf/tr)

cut: 各行から選択した部分を取り出す

cut [-b byte] [-c word] [-f field [ -d sep ] ]  [file-1 file-2 ...]
  • b: バイト単位で取り出す
  • c: 文字数単位で取り出す
  • f: フィールド単位で取り出す。
    • d: 区切り文字 sep はタブがデフォルト。
  • フィールドの指定方法(フィールド番号≧1):バイト・文字数についても同様。
    -f 3 3番目のフィールド
    -f 2-5 2-5番目のフィールド
    -f 1,5 1番目と5番目のフィールド
    -f 5- 5番目以降のフィールド
  • ファイル名を指定しない場合、又は "-" を指定した場合は標準入力から読み込む。

paste: ファイルの横結合(行単位での結合)

paste [-d sep] [file-1 file-2 ...]
  • d: 区切り文字 sep はタブがデフォルト。
  • ファイル名を指定しない場合、又は "-" を指定した場合は標準入力から読み込む。

Tips: 列を指定して文字列を取り出す

  • 2列目と4列目を取り出す例
    cat test.txt | awk '{ print $2 $4 }'
    

Tips: 指定した区切り文字で区切られた文字列から最後のフィールドを取り出す

  • 拡張子を取り出す例(ファイル本体に.が入っていても大丈夫)
    echo "test0.1.txt" | awk -F . '{ print $NF }'
    

Tips: マッチしている以外の行を取り出す

grep -v 〜

Tips: マッチしたファイルのファイル名を表示する

grep -l 〜

Tips: 桁数を指定して数値の先頭を0で埋める

  • その1
    VALUE=12
    VALUE=$( printf "%04d" ${VALUE} )
    
  • その2
    VALUE=12
    VALUE=$( echo ${VALUE} | awk '{ printf("%04d\n",$1) }' )
    

Tips: 先頭の0を削除する

VALUE=0012
VALUE=$( echo ${VALUE} | awk '{ printf("%d\n",$1) }' )

Tips: 右詰め

VALUE=12
VALUE=$( printf "%4d" ${VALUE} )

Tips: 右詰めその2

awk '{ printf("%20s",$1) }'

Tips: 連続する空白を1つにする

echo "a  b c   d" | tr -s " "
  • cut -f で処理する場合に便利

Tips: 小文字を大文字に変換

$ echo "This is a pen." | tr "a-z" "A-Z" 

テキストフィルタ(sed)

Tips: 先頭行を削除する

sed (ファイル名) -e '1d'

Tips: 任意の行を削除する

sed (ファイル名) -e '3,7d'

Tips: 最終行を削除する

sed (ファイル名) -e '$d'

Tips: 空行(空白のみの行も含む)を削除する

sed (ファイル名) -e '/^ *$/d'

Tips: 1行目のみについて、マッチした場合削除

sed (ファイル名) -e '1{ /正規表現/d }'

Tips: マッチ行に囲まれた複数行を取り出す

  • 「正規表現(最初)」〜「正規表現(最後)」のみを表示する
    sed (ファイル名) -e "/正規表現(最初)/,/正規表現(最後)/p" -e d
    

Tips: マッチ行に囲まれた複数行を削除する

  • 「正規表現(最初)」〜「正規表現(最後)」を削除
    sed (ファイル名) -e "/正規表現(最初)/,/正規表現(最後)/d"
    

Tips: 行を指定して文字列を取り出す

  • 5行目を取り出す例
    cat (ファイル名) | sed -e "5,5p" -e d
    

Tips: 空白区切りの文字列に一定間隔で改行を挿入する

STR="1 2 3 4 5 6 7 8 9 10 11 12"
N=3
STR=$( echo ${STR} | sed -e "s/\(\([^ ]\+ \+\)\{${N}\}\)/\1\n/g" )
echo "${STR}"
  • 注意:echo時に""で囲まないと改行は空白として扱われるので注意!

Tips: マッチした行の次の次の行のみを取り出す

  • "abc"にマッチした行の次の次の行を取り出す例
    sed -n -e '/abc/{n;n;p;}'
    
    • そんなに自信があるわけではないが・・・

find

Tips: 複数の条件で検索する

find . -name "name1" -o -name "name2" -o -name "name3" ...

Tips: findで検索したディレクトリを消去

find output_* -name "200406.new.prun2010.gl05" -type d -print0 | xargs -0 rm -r 
  • find の -print0 は空白の代わりにヌル文字を区切り文字とする。
  • xargsの -0 はヌル文字を区切り文字とする。
  • こうすることでディレクトリリストに空白等の特殊文字が入っていても対処できる。

Tips: ディレクトリのみの権限を変更(再帰的)

find * -type d -exec chmod o+x {} \;
  • {}の部分にfindでmatchしたディレクトリ名が置き換わる。
  • \は-execの終端符、ただしエスケープが必要。

その他

終了ステータス

  • 0: 正常終了
  • 0以外: 異常終了
  • command1 が正常終了したら command2 を実行する
    command1 && command2
    
  • command1 が異常終了したら command2 を実行する
    command1 || command2
    

Tips: 複数行のコメントアウト

: <<'#_COMMENT_OUT_'
〜この間の処理は全て無効〜
#_COMMENT_OUT_

ちなみに : は「何もしないコマンド」。このコマンドに標準入力でコメントにしたい部分を読み込む形で、複数行コメントを実現している。

Tips: 複数のコマンドを1行に記述

{ コマンド1; コマンド2; ... }

最後のコマンドの後ろにも;が必要なので注意。

Tips: 深いディレクトリを一気に作成

mkdir -p dir1/dir2/dir3

ディレクトリが既に存在する場合でもエラーにはならない。

Tips: 物理的な絶対パスを取得する

pwd -P

シンボリックリンクを使っている場合は解決される。