Skip to content

Instantly share code, notes, and snippets.

@cmplstofB
Last active February 27, 2024 13:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cmplstofB/0b0ce2bf052b3bb64d091fc83504fc32 to your computer and use it in GitHub Desktop.
Save cmplstofB/0b0ce2bf052b3bb64d091fc83504fc32 to your computer and use it in GitHub Desktop.
POSIX仕類即譜常套句・諸手法

POSIX仕類即譜常套句・諸手法

始めに設定しておく事柄

set -o errexit #(1)
set -o nounset #(1)
set -o noclobber #(1)
umask 0027 #(2)
export PATH="$(command -p getconf PATH)${PATH:+:$PATH}" #(3)
export POSIXLY_CORRECT='200809' UNIX_STD='2003' \
       COMMAND_MODE='unix2003' XPG_SUS_ENV='ON' #(4)

atexit() { set -- $?
	...
	trap - EXIT
	exit $1
}

trap 'atexit' EXIT HUP INT QUIT ABRT ALRM TERM

解説

  1. set組み込み命令による一連の設定

    errexit

    命令が失敗した際,直ちに即譜全体を終了(例外あり)。

    nounset

    未設定の変数を参照した際に終了。

    noclobber

    出力転送演算子>による既存譜類の上書きを防止する。

  2. 譜類作成時の許可設定 これ、PATH設定の前でも良いの?

  3. `$PATH`変数設定。

  4. 機構をPOISXに準拠させる

    POSIXLY_CORRECT

    GNU系 (参考: 標準準拠 - GNU Coreutils

    UNIX_STD

    HP-UX (参考: HP-UX 11i 3版 変更通知452頁)

    COMMAND_MODE

    macOS (参考: compat(5)

    XPG_SUS_ENV

    IBM AIX (参考: Korn仕類 - IBM AIX V7.2

雑題

引数処理

OPTIND=1; while getopts 'o:' OPTNAME; do
	case $OPTNAME in
		'o') (応付子‘o’に対する処理 $OPTARG);;
	esac
done; shift $(($OPTIND - 1))

違了

die() {
# 文言を表示して不成功する
# 使用法: die [-s <exit_status>] [<message>]
# 被演算子:
# 	<exit_status> 終了状態
#                  (既定は直前の返り値か,もしもそれが0なら1)
# 	<message>     表示する文言
	_exit_status="$?"
	OPTIND=1; while getopts 's:' _opt; do
		case $_opt in
			's') _exit_status="$OPTARG" ;;
		esac
	done; shift $(($OPTIND - 1))
	if [ $# -ge 1 ]; then
		printf '[%s] \033[1m\033[31m誤り\033[39m\033[22m: %s\n' \
		  "${0##*/}" "$1"
		shift
	else
		printf '[%s] \033[1m\033[31m誤り\033[39m\033[22m\n' \
		  "${0##*/}"
	fi
	exit $(($_exit_status?$_exit_status:1))
} >&2

言語符号

POSIX地域特性の場合はそれに対応した文字列,そうでなければ例え不正な形式であろうと言語符号と見做される部分を抜き出す。

if [ "${LC_MESSAGES=$(locale 2> '/dev/null' | \
      sed -n -e 's/^LC_MESSAGES="\([^"]*\)"$/\1/p')}" = 'C' \
  -o "$LC_MESSAGES" = 'POSIX' ];then
	echo 'und-u-va-posix'
else
	echo "${LC_MESSAGES%%_*}"
fi

端末応答を取得する

# '\033[>c'を送ると第2端末特性応答が返ってくる。
# 他の応答も同様。
gettermrsp() {
	set -- "$(stty -g)"
	stty -echo -icanon min 0 time 1
	printf '\033[>c' > /dev/tty &&
	< /dev/tty cat
	stty "$1"
}

解説

(未稿)

`gettermrsp()`函数の利用例

曖昧文字幅を取得

1なら半角,2なら全角。

./gettermrep.sh '\0337\033[1;1H\033[8mα\033[0m\033[1D\033[6n\0338' |
sed -n -e 's/'$'\033''\[[[:digit:]]\{1,\};\([[:digit:]]\{1,\}\)R/\1/p'

バイト順判定

isbigend() {
	return \
        $(printf '\001' | od -A n -t o2 | cut -c 6)
}

解説

(未稿)

UTF-8バイト列からUnicode符号位置への復号

所与のUTF-8バイト列をUnicode符号位置へと復号(TODO: これ,「符号化」かも知れない……)する。 Unicodeの表記法は, 『Unicode標準 13.0版』附属書A「表記の取決め」 の表A-1「拡張BNF」を採用した。

不真面目な方法

iconv(1)命令自体はPOSIXに規定されているものの,それがUTF-8及びUCS-4BEの入出力に対応しているとは限らない。 従ってこの方法は真の意味でPOSIXに準拠しているとは言えず,可搬ではない。

とは言え,近代的な運用機構であれば,Unicodeへの対応がある程度見込めるので,ある程度有用ではあるだろう。

iconv -f UTF-8 -t UCS-4BE |
od -A n -t x1 -v |
sed -e 's/'\
' \([[:xdigit:]]\{2\}\)'\
' \([[:xdigit:]]\{2\}\)'\
' \([[:xdigit:]]\{2\}\)'\
' \([[:xdigit:]]\{2\}\)'\
'/\\U\1\2\3\4/g;s/U0000/u/g'

実直な方法

『Unicode標準 13.0版』3章「適合性」 3.9「Unicode符号化形式」にある表3-7「整形式UTF-8バイト列」(127頁)を参考にして,解析する。

解説

まず,入力バイト列を1バイトずつ区切り16進数で書き出す。 od(1)命令には行番号を付けたり同一バイトの連続を省略したりする機能があるが, これを無効にする。

(すいません,未稿です)

日時

当機構での等時帯と協定世界時との時差を求める

#!/bin/sh

set -o errexit
set -o nounset
set -o noclobber
umask 0022
export PATH="$(command -p getconf PATH)${PATH:+:$PATH}"
export POSIXLY_CORRECT='200809' UNIX_STD='2003' \
               COMMAND_MODE='unix2003' XPG_SUS_ENV='ON'

set -- $(date -u +'%Y 1%j 1%H 1%M' && \
         date    +'%Y 1%j 1%H 1%M') # (1)

# +5:45 Nepal
#set -- 2020 1366 123 159 2021 1001 105 145
# -3:30 Canada
#set -- 2020 1001 103 128 2019 1365 123 159

set -- "$(( ($6 - ($1 == $5 ? $2 : $6 + ($1 - $5))) * 1440 \
          + ($7 - $3) * 60 + ($8 - $4) ))" # (2)
printf '%+03d:%02d\n' \
         "$(( $1 / 60 ))" \
         "$(( ${1%%[!-]*}1 * ($1 - ($1 % 5 && 1)) % 60 ))" # (3) (4)

解説

<1> まず,現在の協定世界時および機構標準時の出力を得る。通年日(%j)・時(%H)・分(%M)は各々0で頭埋めされた出力であるが,後に算術展開で扱う際に先頭の0が8進数標識だと見做されるのを避けるべく1を前置する。結局,両者(協定世界時及び機構標準時)の差分を取る過程で,先頭の1は打ち消し合う。また,この過程において,協定世界時の方が機構標準時より必ず先んじて処理・出力される(&&‌制御子の役割)為,もしも両者の実行の間に分を跨いだ場合,機構標準時の方が必ず遅れている(後述の処理に使用)。

<2> 次に,協定世界時及び機構標準時の通年日・時・分それぞれの差分を分を単位として取る。この過程で,時・日跨ぎ(注意: 命令実行の間に跨いだ場合に限らない。協定世界時との時差が+09:00で,協定世界時が23:50だった場合を考えよ)に関しては,それぞれ時($3`と$7`)・通年日($2`と$6`)の差分を加えることによって適切に対処できる。通年日の差分を取る際,年跨ぎを考慮(年($1`と$5`)の値の相等で判定)して,年の差(±1)を機構標準時の通年日に足し合わせることで帳消す。

<3> ここで,(両者の実行の間に←?)分を跨いだ場合の処理をする。協定世界時との時差の最小単位は15分であり,協定世界時及び機構標準時の出力の間で分を跨いだ場合には機構標準時の方が必ず遅れていることを踏まえると,(分での)差分の(符号付き)下1桁について,協定世界時との時差が正の場合には「1」または「6」(従って-1で帳消し)であり,負の場合には「-4」なたは「-9」(従って-1で帳消し)となる筈である。故に,分跨ぎは,差分が5の倍数でない場合に差分から1を除くという処理で適切に対処できる。猶,分跨ぎに伴って時・日・年を跨ぐ場合もあるが,分跨ぎに由来しない差分に関しては前述までの処理で適切に打ち消されている。

<4> 最後に,分での差分を時・分に分ける。この時,時に関しては差分の正負を明示して出力し,分に関しては絶対値を取る。

実行主が特権管理者かどうか判定

isadm() { [ $(id -u) -eq 0 ] || return 77; }

解説

残念ながらPOSIXでは特権管理者 (所謂 “root” とか “admin” とか) の用者識別子を明確に定義していない( 定義 / 3.436使用者識別子 にその趣旨の記載が見当らない)ものの, 通則0であるようだ(参考: getpwuid / 特権管理者の項目を取得する )。 これに拠って,現在の用者識別子が0であれば特権管理者と見做すことにする。

与件の引数列にある,特定の引数を編集する

for _a in "$@"; do
shift
# 一致する条件
# 編集処理
# そうでない条件
set -- "$@" "$_a"
done

(解説未稿)

“-d” という引数を削除するが,その他の引数は順番も考慮して保存する。

set -- -a -b c -d -x # これは本来外部で与えられる。
for _a in "$@"; do
shift
case $_a in
	-d) ;;
	*) set -- "$@" "$_a" ;;
esac
done

(附録)DOAS/Turtleによる即譜の情報

(これは即譜の実行等には直接影響しない為,附録とする)

: '@prefix : <http://purl.org/net/ns/doas#>. <> a :Script;
:一行説明   "即譜の説明。";
:作成日     "YYYY-MM-DD";
:公開版     ( [:版 "0.1.1"; :作成日 "YYYY-MM-DD"]
              [:版 "0.1.0"; :作成日 "YYYY-MM-DD"] );
:作成者     "cmplstofB";
:権利       "ⓒ YYYY cmplstofB";
:ライセンス <http://www.wtfpl.net/txt/copying/>;
:依存関係   "POSIX.1-2017".'
@prefix : <http://purl.org/net/ns/doas#>. <> a :Script;
:一行説明   "即譜の説明。";
:作成日     "YYYY-MM-DD";
:公開版     ( [:版 "0.1.1"; :作成日 "YYYY-MM-DD"]
              [:版 "0.1.0"; :作成日 "YYYY-MM-DD"] );
:作成者     "cmplstofB";
:権利       "ⓒ YYYY cmplstofB";
:ライセンス <http://www.wtfpl.net/txt/copying/>;
:依存関係   "POSIX.1-2017".

(解説未稿)

#!/bin/sh
# https://www.unicode.org/versions/Unicode13.0.0/ch03.pdf『Unicode標準 13.0版』3章「適合性」
# 3.9「Unicode符号化形式」表3-7「整形式UTF-8バイト列」
# + 符号位置 --------------+ 第1バイト -+ 第2バイト -+ 第3バイト -+ 第4バイト -+
# | \u0000..\u007F | 0x00..0x7F | | | |
# | \u0080..\u07FF | 0xC2..0xDF | 0x80..0xBF | | |
# | \u0800..\u0FFF | 0xE0 | 0xA0..0xBF | 0x80..0xBF | |
# | \u1000..\uCFFF | 0xE1..0xEC | 0x80..0xBF | 0x80..0xBF | |
# | \uD000..\uC7FF | 0xED | 0x80..0x9F | 0x80..0xBF | |
# | \uE000..\uFFFF | 0xEE..0xEF | 0x80..0xBF | 0x80..0xBF | |
# | \U00010000..\U0003FFFF | 0xF0 | 0x90..0xBF | 0x80..0xBF | 0x80..0xBF |
# | \U00040000..\U000FFFFF | 0xF1..0xF3 | 0x80..0xBF | 0x80..0xBF | 0x80..0xBF |
# | \U00100000..\U0010FFFF | 0xF4 | 0x80..0x8F | 0x80..0xBF | 0x80..0xBF |
# +------------------------+------------+------------+------------+------------+
# https://www.unicode.org/versions/Unicode13.0.0/appA.pdf『Unicode標準 13.0版』附属書A「表記の取決め」
# 表A-1「拡張BNF」
: '@prefix : <http://purl.org/net/ns/doas#>. <> a :Script;
:一行説明 "UTF-8バイト列からUnicode符号位置へ変換する。";
:作成日 "2020-03-28";
:公開版 ( [:版 "0.1.0"; :作成日 "2020-03-28"] );
:作成者 "cmplstofB";
:権利 "ⓒ 2020 cmplstofB";
:ライセンス <http://www.wtfpl.net/txt/copying/>;
:依存関係 "POSIX.1-2017".'
set -o errexit
set -o nounset
set -o noclobber
umask 0022
export PATH="$(command -p getconf PATH)${PATH:+:$PATH}"
export POSIXLY_CORRECT='200809' UNIX_STD='2003' \
COMMAND_MODE='unix2003' XPG_SUS_ENV='ON'
${_flag_unsafe+:} false && unset -v _flag_unsafe || :
OPTIND=1; while getopts hc _opt; do
case $_opt in
'h') <<-. cat
使用法: ${0##*/} [-u]
オプション:
-u 安全性を犠牲にして処理速度を少し上げる(未実装)。
この場合,入力を整形式UTF-8バイト列と決め付け,検査しない。
.
exit;;
'u') _flag_unsafe=
esac
done; shift $(($OPTIND - 1))
{ ${1+:} false && printf '%s' "$1" || cat; } |
od -v -A nx -t x1 | # <1>
tr -C -s '[:xdigit:]' '\n' |
sed -n -e '
/^[0-7][0-9abcdef]/{
s/\([[:xdigit:]]\{2\}\)/\
printf "\\\\\\u00%02X\\n" \\\
$(( 0x\1 ))/p
}
/^c[2-9abcdef]/{
h;n;G
s/\([[:xdigit:]]\{2\}\)\n\([[:xdigit:]]\{2\}\)/\
printf "\\\\\\u%02X%02X\\n" \\\
$(( 0x\2 \/ 4 - 0x30 )) \\\
$(( 0x\1 - 0x80 + (0x\2 % 4) * 0x40 ))/p
}
/^d[0-9abcdef]/{
h;n;G
s/\([[:xdigit:]]\{2\}\)\n\([[:xdigit:]]\{2\}\)/\
printf "\\\\\\u%02X%02X\\n" \\\
$(( 0x\2 \/ 4 - 0x30 )) \\\
$(( 0x\1 - 0x80 + (0x\2 % 4) * 0x40 ))/p
}
/^e0/{
h;n;G
h;n;G
s/\([[:xdigit:]]\{2\}\)\n\([[:xdigit:]]\{2\}\)\n\([[:xdigit:]]\{2\}\)/\
printf "\\\\\\u%02X%02X\\n" \\\
$(( 0x\2 \/ 4 - 0x20 )) \\\
$(( 0x\1 - 0x80 + (0x\2 % 4) * 0x40 ))/p
}
/^e[1-9abc]/{
h;n;G
h;n;G
s/\([[:xdigit:]]\{2\}\)\n\([[:xdigit:]]\{2\}\)\n\([[:xdigit:]]\{2\}\)/\
printf "\\\\\\u%1X%1X%02X\\n" \\\
$(( 0x\3 % 16 )) \\\
$(( 0x\2 \/ 4 - 0x20 )) \\\
$(( 0x\1 - 0x80 + (0x\2 % 4) * 0x40 ))/p
}
/^ed/{
h;n;G
h;n;G
s/\([[:xdigit:]]\{2\}\)\n\([[:xdigit:]]\{2\}\)\n[[:xdigit:]]\{2\}/\
printf "\\\\\\ud%1X%02X\\n" \\\
$(( 0x\2 \/ 4 - 0x20 )) \\\
$(( 0x\1 - 0x80 + (0x\2 % 4) * 0x40 ))/p
}
/^e[ef]/{
h;n;G
h;n;G
s/\([[:xdigit:]]\{2\}\)\n\([[:xdigit:]]\{2\}\)\n\([[:xdigit:]]\{2\}\)/\
printf "\\\\\\u%1X%1X%02X\\n" \\\
$(( 0x\3 % 16 )) \\\
$(( 0x\2 \/ 4 - 0x20 )) \\\
$(( 0x\1 - 0x80 + (0x\2 % 4) * 0x40 ))/p
}
/^f0/{
h;n;G
h;n;G
h;n;G
s/\([[:xdigit:]]\{2\}\)\n\([[:xdigit:]]\{2\}\)\n\([[:xdigit:]]\{2\}\)\n[[:xdigit:]]\{2\}/\
printf "\\\\\\U000%1X%02X%02X\\n" \\\
$(( 0x\3 \/ 16 - 0x08 )) \\\
$(( 0x\2 \/ 4 - 0x20 + (0x\3 % 16) * 16 )) \\\
$(( 0x\1 - 0x80 + (0x\2 % 4) * 0x40 ))/p
}
/^f[1-3]/{
h;n;G
h;n;G
h;n;G
s/\([[:xdigit:]]\{2\}\)\n\([[:xdigit:]]\{2\}\)\n\([[:xdigit:]]\{2\}\)\n\([[:xdigit:]]\{2\}\)/\
printf "\\\\\\U000%1X%02X%02X\\n" \\\
$(( 0x\3 \/ 16 - 0x04 + (0x\4 - 0xf1) * 0x04 )) \\\
$(( 0x\2 \/ 4 - 0x20 + (0x\3 % 16) * 16 )) \\\
$(( 0x\1 - 0x80 + (0x\2 % 4) * 0x40 ))/p
}
/^f4/{
h;n;G
h;n;G
h;n;G
s/\([[:xdigit:]]\{2\}\)\n\([[:xdigit:]]\{2\}\)\n\([[:xdigit:]]\{2\}\)\n[[:xdigit:]]\{2\}/\
printf "\\\\\\U0010%02X%02X\\n" \\\
$(( 0x\2 \/ 4 - 0x20 + (0x\3 % 16 << 4) )) \\\
$(( 0x\1 - 0x80 + (0x\2 % 4) * 0x40 ))/p
}
' |
sh
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment