Parameter Expansion
-
${#var}
:$var
の長さ
String の一部を取り除く
-
${var#pattern}
文字列の初めからもっともショートマッチした部分を削除 -
${var##pattern}
文字列の初めから、もっとロングマッチした部分を削除 -
${var%pattern}
文字列の最後からもっともショートマッチした部分を削除 -
${var%%pattern}
文字列の最後からもっともロングマッチした部分を削除
*, ?, []
などもブラケットの中で使用可能。
Example i="/Users/Tsuyoshi/demo.txt"
${i#*/} Users/Tsuyoshi/demo.txt
${i##*/} demo.txt
${i%.*} /Users/Tsuyoshi/demo
${i%/*} /Users/Tsuyoshi
Search と Replace
${var/pattern/string} 最初のマッチを置き換える
${var//pattern/string} 全てのマッチを置き換える
${var/#pattern/string} 最初の文字を置き換える
${var/%pattern/string} 最後からマッチしたものを置き換える
demo
$i="myjpg.jpg"
$echo ${i/jpg/txt}
mytxt.jpg
$echo ${i/%jpg/txt}
myjpg.txt
$echo ${i//jpg/txt}
mytxt.txt
$echo ${i//jpg}
my.
$echo ${i/%jpg/}
myjpg.
$echo ${i#jpg}
myjpg.jpg # String がjpg で始まっていないため。
$echo ${i/[yj]/a}
majpg.jpg
$echo ${i//[yj]/a}
maapg.apg
mvext
# !/bin/bash
[[ $# -ne 2 ]] && { echo "Missing two arguments" >&2; exit 1; }
for f in *"$1"; do
mv "$f" "${f/%$1/$2}"
done
実行
$ls
a.jpg b.jpg c.jpg
$mvext jpg txt
$ls
a.txt b.txt c.txt
Default value
${var:-value}
${var-value}
var 空のケースに限る (上記の省略系)
${var:=value}
varがカラだったり、セットされていないケースにアサインする
${var=value}
var がunsetの時のみ
サンプル
次の例は$NOTESDIR
がある場合は、その値、なければ $HOME
が使われる。
declare notesdir=${NOTESDIR:-$HOME}
コンディショナル
Pattern matching
-
[[ .. ]]
の中の==
!=
はパターンマッチングを行う -
[[ $var == pattern ]]
は$var
がパターンにマッチすると true を返す - パターンマッチは、
*, ? , []
などを用いる - ダブルクオートで囲むと、パターンマッチではなく、文字列として比較を行う
サンプル
パターンマッチが上記の通り行われている。ダブルクオートだと、パターンマッチではなく、文字列そのものとしてマッチする。最後のものは、間違えて[ .. ]
にしたもの。この場合、t*i
は、ファイル名として解釈される。
$[[ tsuyoshi = t*i ]] && echo yep
yep
$[[ tsuyoshi = "t*i" ]] && echo yep
$[[ "t*i" = "t*i" ]] && echo yep
yep
$[ tsuyoshi = t*i ] && yep
$ls
a.txt box count d.txt hw read_pipe
b.txt c.txt create_script hist mvext test
$echo [ m*t ]
[ mvext ]
$
Regular Expression
正規表現
-
=~
正規表現のマッチ
?
?
はトークンが0もしくは1回の出現にマッチする。[0-9]?
は1文字の数字もしくは、何もない変数にマッチする。
*
*
は全てのトークンにマッチする。[a-z]*
は全ての小文字の英字もしくは、何もないのにマッチする。
+
+
は1回以上にマッチする。[0-9]+
は、1回以上の数字が出て来ることにマッチする
^
文字の開始にマッチする
$
文字の終了にマッチする
.
任意の一文字
サンプル
以前のカウントを改造する。isnum
メソッドを改造してパターンマッチを使う。ポイントとしては、最初に、変数に正規表現を代入して使っている。シングルクオートを使っている。これは、エスケープをするのが面倒なので、シングルクオートを使って、変数に代入することでそれを避けられるから、コードが楽になる。
^[0-9]+$
というのは、先頭文字^
から、数字[0-9]
何文字か+
最後まで$
という意味なので、任意の数字にヒットする。^0(.+)
というのは、先頭^
が0その後続は任意の文字(複数)(.+)
という意味。つまり先頭が0でそのあとは任意。数字チェックをしたif の中にいるので、当然数字で0始まり、つまり、8進数表現を表している。(Bashの場合)。
ちなみに、$BASH_REMATCH
は正規表現にマッチした内容が配列になって入っているもの。$BASH_REMATCH[0]
は全体で、$BASH_REMATCH[1]
以降はマッチの部分が入っていく。この場合は、0を除いた数字が入る。
isnum() {
declare -r num_re='^[0-9]+$'
declare -r octal_re='^0(.+)'
num_error="ok"
if [[ $1 =~ $num_re ]]; then
if [[ $1 =~ $octal_re ]]; then
num_error="$1 is not a number, did you mean ${BASH_REMATCH[1]}?"
return 1
fi
else
num_error="$1 is not a number"
return 1
fi
return 0
}
実行
実行するとちゃんと8進数が弾かれる。0も、^0(.+)
で+
が一文字以上なので、マッチしないので問題ない。
$count 020
Error: 020 is not a number, did you mean 20?
count [-r] [-b n] [-s n] stop
Print each number up to stop, beginning at 0
-b: number to begin with (default: 0)
-h: show this help message
-r: reverses the count
-s: sets step size (default: 1)
$count 0
0
全てのコード
count
# !/bin/bash
usage () {
cat <<END
count [-r] [-b n] [-s n] stop
Print each number up to stop, beginning at 0
-b: number to begin with (default: 0)
-h: show this help message
-r: reverses the count
-s: sets step size (default: 1)
END
}
error() {
echo "Error: $1"
usage
exit $2
} >&2
isnum() {
declare -r num_re='^[0-9]+$'
declare -r octal_re='^0(.+)'
num_error="ok"
if [[ $1 =~ $num_re ]]; then
if [[ $1 =~ $octal_re ]]; then
num_error="$1 is not a number, did you mean ${BASH_REMATCH[1]}?"
return 1
fi
else
num_error="$1 is not a number"
return 1
fi
return 0
}
declare reverse=""
declare -i start=0
declare -i step=1
while getopts ":hb:s:r" opt; do
echo "OPTARG: ${OPTARG}"
case $opt in
r)
reverse="yes"
;;
h)
usage
exit 0
;;
b)
isnum ${OPTARG} || error "${num_error}" 1
start="${OPTARG}"
;;
s)
isnum ${OPTARG} || error "${num_error}" 1
step="${OPTARG}"
;;
:)
echo "Option -${OPTARG} is missing an argument"
exit 1
;;
\?)
echo "Unknown option: -${OPTARG}" >&2
exit 1
;;
esac
done
shift $(( OPTIND -1 ))
[[ $1 ]] || { echo "Argument is missing" >&2; exit 1; }
declare end="$1"
isnum $1 || { error "${num_error}" 1 ; exit 1; }
if [[ ! $reverse ]]; then
for (( i=start; i <= end; i+=step )); do
echo $i
done
else
for (( i=end; i >= start; i-+step )); do
echo $if
done
fi
exit 0
End of options
--
この後の引数は、評価されない。例えば-l.txt
というファイルがあるとすると、引数と勘違いされる。rm -- -l.txt
とやるとうまくいく。そういったファイル名を含む場合に、ファイル操作したい場合、
$ for in *.txt; do touch -- $i; done
と言った書き方もできる。引数としての変数にを使うときに、使うのは良い習慣。引数は、自分でコントロールできないので、コマンドを呼ぶときに、--
を使う
サンプル
-a.txt
というファイル名は通常作れませんが、--
を使うと、可能になります。
$touch -a.txt
touch: illegal option -- .
usage:
touch [-A [-][[hh]mm]SS] [-acfhm] [-r file] [-t [[CC]YY]MMDDhhmm[.SS]] file ...
$touch -- -a.txt
$ls
-a.txt a.txt b.txt c.txt
既存のプログラムmvext
を動作させてみますが、このようなファイル名が含まれている場合は動きません。
$mvext txt jpg
mv: illegal option -- a
usage: mv [-f | -i | -n] [-v] source target
mv [-f | -i | -n] [-v] source ... directory
出来るように改造してみます。mv
の所に --
をつけて、パラメータと思われないようにします。
mvext
# !/bin/bash
[[ $# -ne 2 ]] && { echo "Missing two arguments" >&2; exit 1; }
for f in *"$1"; do
mv -- "$f" "${f/%$1/$2}"
done
すると
$mvext txt jpg
$ls
-a.jpg a.jpg b.jpg c.jpg
綺麗に動作します。他にも、パラメータではない部分には、入れといたほうが良さそうです。
tn
# !/bin/bash
declare -r date=$(date)
declare -r topic="$1"
declare notesdir=${NOTESDIR:-$HOME}
if [[ ! -d $notesdir ]]; then
mkdir -- "${notesdir}" 2> /dev/null || { echo "Cannot make directory ${notesdir}" 1>&2 ; exit 1; }
fi
declare -f filename="${notesdir}/${topic}notes.txt"
if [[ ! -f $filename ]]; then
touch -- "${filename}" 2> /dev/null || { echo "Cannot create file ${filename}." 1>&2 ; exit 1;}
fi
[[ -w $filename ]] || { echo "${filename} is not writable" 1>&2; exit 1; }
read -p "Your note: " note
if [[ $note ]]; then
echo "$date: $note" >> "$filename"
echo "Note '$note' saved to $filename" 1>&2
else
echo "No input; note wasn't saved." 1>&2
exit 2
fi
exit 0