読者です 読者をやめる 読者になる 読者になる

😃 mattintosh note 📝

Hello Raspberry Pi!

シェルスクリプトの雑記帳

Mac OS X ShellScript

シェルの初期化

env を経由することにより環境変数などをリセットできる。

#!/usr/bin/env - SHELL=/bin/sh LC_ALL=C TERM=xterm COMMAND_MODE=unix2003 /bin/sh

ただし以下のように実行された場合はリセットされない。

$ sh script.sh

最低限?必要な環境変数の取得方法。システム環境変数は getconf、ユーザー環境変数は launchctl getenv かな。もし絶対パスでファイル指定が可能ならシェバングに ENV=/path/to/file や BASH_ENV=/path/to/file で source コマンドのように環境変数などを読み込ませることができる。

name command
PATH /usr/sbin/sysctl -n user.cs_path(システム)
getconf PATH(システム)
launchctl getenv PATH
/usr/libexec/path_helper
TMPDIR getconf DARWIN_USER_TEMP_DIR
launchctl getenv TMPDIR
HOME launchctl getenv HOME
DISPLAY launchctl getenv DISPLAY

PATH を改行区切りで

コロン使うと長くなるので改行区切りでなんとか。最後に %? で1文字削る。

unset PATH; while read; do PATH+=$REPLY:; done <<@EOS; export PATH=${PATH%?}
/usr/local/bin
/usr/local/sbin
/usr/bin
/bin
/usr/sbin
/sbin
@EOS

tr とか。

$ PATH=$(tr '\n' ':' <<@EOS
/usr/local/bin
/usr/local/sbin
/usr/bin
/bin
/usr/sbin
/sbin
@EOS); export PATH=${PATH%?}
$ echo $PATH
/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin

配列を特定の文字で区切って出力

配列変数がダブルクオートで囲まれていて、なおかつ * で呼び出すと IFS の最初の文字がデリミタになる。

$ (IFS='
'; echo "${BASH_VERSINFO[*]}")
3
2
48
1
release
x86_64-apple-darwin10.0

zsh っぽく path を配列にして PATH に変換してみたり。

$ path=(
/usr/bin
/bin
/usr/sbin
/sbin
)
$ PATH=$(IFS=:; echo "${path[*]}")
$ echo $PATH
/usr/bin:/bin:/usr/sbin:/sbin

サブシェルで local っぽく

サブシェル内で引数や変数が変更されても親には影響しないので local のように使える。set -e してる場合、この部分は失敗しても exit しないので || false とか書いておく。

$ set -- /usr/local
$ (set -- $1/foo/bar && mkdir -p $1 && cp a b c $1) || false
$ echo $@
/usr/local

IFS を一時的に変更してみたり。これは tr でできるけど。

$ echo "a,b,c" > foo.csv
$ (IFS=,; for x in $(cat foo.csv); do echo $x; done)
a
b
c

関数の色々な書き方

シェルスクリプトの関数に設定するコマンド部分は "複合コマンド" なので {} の他に () を使うことができる。

$ foo ()(cd /tmp && pwd)
$ pwd
/usr/local
$ foo
/tmp
$ pwd
/usr/local

for などは 予約語 ~ done までが複合コマンドなので括弧が無くてもいい(読みづらいけど)。

$ foo () for x in "$@"; do echo $x; done
$ foo a b c
a
b
c
$ foo () for x in "$@"
> do
> echo $x
> done
$ foo a b c
a
b
c

関数名に「@」

$ @foo (){ echo "$@"; }
$ @foo hoge
hoge

ただし sh(POSIX モード)では使えない。

local の使い道

一度 local するとその変数に対する処理は関数内でのみ適用される。

  • local name だけの場合は値が空になる。
  • 元の値にする場合は local するときに引き継ぐようにする。
  • 関数内でのみ環境変数属性を解除したり変数を削除することができる。
# 値の初期化
fn_a (){
  local CFLAGS
  declare -p CFLAGS
}

# 値の連結
fn_b (){
  local CFLAGS="$CFLAGS -mtune=native"
  declare -p CFLAGS
}

# 環境変数属性の解除
fn_c (){
  local CFLAGS
  declare +x CFLAGS
  declare -p CFLAGS
}

# 変数の削除
fn_d (){
  local CFLAGS
  unset CFLAGS
  declare -p CFLAGS
}

export CFLAGS="-march=native"
printf "current: "
declare -p CFLAGS
printf "fn_a:    "
fn_a
printf "fn_b:    "
fn_b
printf "fn_c:    "
fn_c
printf "fn_d:    "
fn_d
printf "current: "
declare -p CFLAGS
current: declare -x CFLAGS="-march=native"
fn_a:    declare -x CFLAGS=""
fn_b:    declare -x CFLAGS="-march=native -mtune=native"
fn_c:    declare -- CFLAGS=""
fn_d:    functest.sh: line 25: declare: CFLAGS: not found
current: declare -x CFLAGS="-march=native"

環境変数の名前だけを取り出す

sh、bash、zsh で export -p や declare -x、typeset -x の出力が異なるのでこれといった方法が思いつかなかった。sed で整形。

$ declare -x | sed 's/^declare -x //;s/=.*$//'

環境変数を全て削除。PATH も消えるので注意。

$ unset $(declare -x | sed 's/^declare -x //;s/=.*$//')

シェルを指定して取得した方が書式が固定されるので置換が楽かも。

$ sh -c 'export -p'| sed 's/export \([a-zA-Z0-9_]*\)=.*$/\1/'

Python では os.environ.items() で環境変数一覧が表示でき、変数名と値がタプルになっているので変数名だけの取り出しが簡単にできる(__CF_USER_TEXT_ENCODING が余計に入る?)。結果を sort したければ sorted(os.environ.items()) とする。

$ python <<\_EOS
import os
for x in os.environ.items():
    print x[0]
_EOS

Python にあまり詳しくないのでワンライナーならどうするんだろうとちょっと調べて書いてみた。改行とインデント使って書いた方が早いかな…。

$ python -c 'import os, sys; [sys.stdout.write(str(x[0]) + "\n") for x in os.environ.items()]'

while 内で shift する箇所が多いなら先に shift してしまう

while 内で shift することが多い場合。(以下の例は適当)

while [ "$1" ]
do
  case $1 in
    1) command; shift; continue;;
    2) command;;
    3) command; shift; continue;;
    *) command;;
  esac
  command
  shift
done

第1引数にダミーを突っ込んで条件式の時点で shift する。

set -- - "$@"
while shift; [ "$1" ]
do
  case $1 in
    1) command; continue;;
    2) command;;
    3) command; continue;;
    *)
  esac
  command
done

for を使って最後に shift $# とかでもいいかも。

for x in "$@"
do
  command
done
unset x
shift $#

「*」と「?」

クオートされていない * と ? はパス名展開に使われる。これを echo すると \ls っぽい結果が得られる。(ただのスペース区切りだけど)

$ touch a b c def
$ echo *
a b c def
$ echo .*
. ..
$ echo .* .
. .. a b c def
$ echo ?
a b c
$ echo $PWD/?
/tmp/a /tmp/b /tmp/c

配列を2個ずつ3個ずつ…処理する

while の場合は簡単。ただし shift するときに残りの引数が足りないと shift されないので条件式側で止めるようにしておくか、shift 3 || break といった感じで対応しておく。

set -- {0..9}
alen=3
while [ $# -ge $alen ]
do
 echo $1 $2 $3
 shift $alen
done

for の場合別の配列を作成して一定数になったら処理してリセット。それ以外なら continue してチャージしていく感じ。while のように無限ループにはならないけど要素数不足で処理されないものが出てくるかもしれない。

for x in {0..9}
do
  array=(${array[@]} $x)
  if [ ${#array[@]} -ge 3 ]
  then
    echo ${array[@]}
    array=()
  else
    continue
  fi
done

ビルド関連

ビルド時に使用するツール等は ac_cv_prog_NAME などで指定できることがある。

gettext

PATH 上に無い gettext を使用する場合は MSGFMT や ac_cv_prog_MSGFMT などで指定できる(configure による)。

pkg-config

PATH 上に無い pkg-config を使用する場合は PKG_CONFIG や ac_cv_prog_PKG_CONFIG で指定できる(configure による)。

PKG_CONFIG_LIBDIR の取得

pkg-config --variable pc_path pkg-config

AppleScript

「Appleevent がタイムアウトしました」の対応

tell application "Finder"
    with timeout of 600 seconds -- default: 120s
       (* statement *)
    end timeout
end tell

ignoring application responses 〜 end ignoring もある。

open コマンドを AppleScript で

open location "file:///path/to/file"