Skip to content

Instantly share code, notes, and snippets.

@colrichie
Last active September 14, 2017 09:00
Show Gist options
  • Save colrichie/3251311 to your computer and use it in GitHub Desktop.
Save colrichie/3251311 to your computer and use it in GitHub Desktop.
[MOVED] CSV & JSON & XML file scraping tools
#! /bin/sh
#
# parsrc.sh
# CSV(Excel形式(RFC 4180):ダブルクォーテーションのエスケープは"")から
# 行番号列番号インデックス付き値(line field indexed value)テキストへの正規化
# (例)
# aaa,"b""bb","c
# cc",d d
# "f,f"
# ↓
# 1 1 aaa
# 1 2 b"bb
# 1 3 c\ncc
# 1 4 d d
# 2 1 f,f
# ◇よって grep '^1 3 ' | sed 's/^[^ ]* [^ ]* //' などと
# 後ろに grep&sed をパイプで繋げれば目的の行・列の値が得られる。
# さらにこれを
# sed 's/\\n/\<LF>/g' (←"<LF>"は実際には改行を表す)
# にパイプすれば、元データに改行を含む場合でも完全な値として取り出せる。
#
# Usage: parsrc.sh [-lf<str>] [CSV_file]
# Options : -lf は値として含まれている改行を表現する文字列指定(デフォルトは
# "\n"であり、この場合は元々の \ が \\ にエスケープされる)
#
# Written by Rich Mikan(richmikan[at]richlab.org) / Date : Sep 28, 2014
SO=$(printf '\016') # ダブルクォーテーション*2のエスケープ印
SI=$(printf '\017') # 値としての改行文字列エスケープ印
RS=$(printf '\036') # 1列1行化後に元々の改行を示すための印
US=$(printf '\037') # 1列1行化後に元々の列区切りを示すための印
LF=$(printf '\\\n_');LF=${LF%_} # SED内で改行を変数として扱うためのもの
optlf=''
bsesc='\\'
file=''
printhelp=0
i=0
for arg in "$@"; do
i=$((i+1))
if [ \( "_${arg#-lf}" != "_$arg" \) -a \( -z "$file" \) ]; then
optlf=$(printf '%s' "${arg#-lf}_" |
tr -d '\n' |
sed 's/\([\&/]\)/\\\1/g' )
optlf=${optlf%_}
elif [ \( $i -eq $# \) -a \( "_$arg" = '_-' \) -a \( -z "$file" \) ]; then
file='-'
elif [ \( $i -eq $# \) -a \( \( -f "$arg" \) -o \( -c "$arg" \) \) \
-a \( -z "$file" \) ]
then
file=$arg
else
printhelp=1;
fi
done
if [ $printhelp -ne 0 ]; then
cat <<-__USAGE
Usage : ${0##*/} [-lf<str>] [CSV_file] 1>&2
Options : -lf は値として含まれている改行を表現する文字列指定(デフォルトは
"\n"であり、この場合は元々の \ が \\ にエスケープされる)
__USAGE
exit 1
fi
[ -z "$optlf" ] && { optlf='\\n'; bsesc='\\\\'; }
[ -z "$file" ] && file='-'
# === データの流し込み ============================================= #
cat "$file" |
# #
# === 値としてのダブルクォーテーションをエスケープ ================= #
# (但しnull囲みの""も区別が付かず、エスケープされる) #
sed 's/""/'$SO'/g' |
# #
# === 値としての改行を\nに変換 ===================================== #
# (ダブルクォーテーションが奇数個ならSI付けて次の行と結合する) #
awk ' \
BEGIN { \
while (getline line) { \
s = line; \
gsub(/[^"]/, "", s); \
if (((length(s)+cy) % 2) == 0) { \
cy = 0; \
printf("%s\n", line); \
} else { \
cy = 1; \
printf("%s'$SI'", line); \
} \
} \
} \
' |
# #
# === 各列を1行化するにあたり、元々の改行には予め印をつけておく ==== #
# (元々の改行の後にRS行を挿入する) #
awk ' \
{ \
printf("%s\n'$RS'\n", $0); \
} \
' |
# #
# === ダブルクォーテーション囲み列の1列1行化 ======================= #
# (その前後にスペースもあれば余計なのでここで取り除いておく) #
# (1/3)先頭からNF-1までのダブルクォーテーション囲み列の1列1行化 #
sed 's/[[:blank:]]*\("[^"]*"\)[[:blank:]]*,/\1'"$LF$US$LF"'/g' |
# (2/3)最後列(NF)のダブルクォーテーション囲み列の1列1行化 #
sed 's/,[[:blank:]]*\("[^"]*"\)[[:blank:]]*$/'"$LF$US$LF"'\1/g' |
# (3/3)ダブルクォーテーション囲み列が単独行だったらスペース除去だけ #
sed 's/^[[:blank:]]*\("[^"]*"\)[[:blank:]]*$/\1/g' |
# #
# === ダブルクォーテーション囲みでない列の1列1行化 ================= #
# (単純にカンマを改行にすればよい) #
# (ただしダブルクォーテーション囲みの行は反応しないようにする) #
sed '/['$RS'"]/!s/,/'"$LF$US$LF"'/g' |
# #
# === ダブルクォーテーション囲みを外す ============================= #
# (単純にダブルクォーテーションを除去すればよい) #
# (値としてのダブルクォーテーションはエスケープ中なので問題無し) #
tr -d '"' |
# #
# === エスケープしてた値としてのダブルクォーテーションを戻す ======= #
# (ただし、区別できなかったnull囲みの""も戻ってくるので適宜処理) #
# (1/3)まずは""に戻す #
sed 's/'$SO'/""/g' |
# (2/3)null囲みの""だった場合はそれを空行に変換する #
sed 's/^[[:blank:]]*""[[:blank:]]*$//' |
# (3/3)""(二重)を一重に戻す #
sed 's/""/"/g' |
# #
# === 先頭に行番号と列番号をつける ================================= #
awk ' \
BEGIN{ \
l=1; \
f=1; \
while (getline line) { \
if (line == "'$RS'") { \
l++; \
f=1; \
} else if (line == "'$US'") { \
f++; \
} else { \
printf("%d %d %s\n", l, f, line); \
} \
} \
} \
' |
# #
# === 値としての改行のエスケープ(SI)を代替文字列に変換 ============= #
if [ "_$bsesc" != '_\\' ]; then #
sed 's/\\/'"$bsesc"'/g' #
else #
cat #
fi |
sed 's/'"$SI"'/'"$optlf"'/g'
#! /bin/sh
#
# parsrj.sh
# JSONテキストから
# 階層インデックス付き値(tree indexed value)テキスへの正規化
# (例)
# {"hoge":111,
# "foo" :["2\n2",
# {"bar" :"3 3",
# "fizz":{"bazz":444}
# },
# "\u5555"
# ]
# }
# ↓
# $.hoge 111
# $.foo[0] 2\n2
# $.foo[1].bar 3 3
# $.foo[1].fizz.bazz 444
# $.foo[2] \u5555
# ◇よって grep '^\$foo[1].bar ' | sed 's/^[^ ]* //' などと
# 後ろ grep, sed をパイプで繋げれば目的のキーの値部分が取れる。
# さらにこれを unescj.sh にパイプすれば、完全な値として取り出せる。
#
# Usage : parsrj.sh [JSON_file] ←JSONPath表現
# : parsrj.sh --xpath [JSON_file] ←XPath表現
# : parsrj.sh [2letters_options...] [JSON_file] ←カスタム表現
# Options : -sk<s> はキー名文字列内にあるスペースの置換文字列(デフォルトは"_")
# : -rt<s> はルート階層シンボル文字列指定(デフォルトは"$")
# : -kd<s> は各階層のキー名文字列間のデリミター指定(デフォルトは".")
# : -lp<s> は配列キーのプレフィックス文字列指定(デフォルトは"[")
# : -ls<s> は配列キーのサフィックス文字列指定(デフォルトは"]")
# : -fn<n> は配列キー番号の開始番号(デフォルトは0)
# : -li は配列行終了時に添字なしの配列フルパス行(値は空)を挿入する
# : --xpathは階層表現をXPathにする(-rt -kd/ -lp[ -ls] -fn1 -liと等価)
# : -t は、値の型を区別する(文字列はダブルクォーテーションで囲む)
#
# Written by Rich Mikan(richmikan[at]richlab.org) / Date : Sep 27, 2014
DQ=$(printf '\026') # 値のダブルクォーテーション(DQ)エスケープ用
LF=$(printf '\\\n_');LF=${LF%_} # sed内で改行を変数として扱うためのもの
file=''
sk='_'
rt='$'
kd='.'
lp='['
ls=']'
fn=0
unoptli='#'
optt=''
unoptt='#'
for arg in "$@"; do
if [ \( "_${arg#-sk}" != "_$arg" \) -a \( -z "$file" \) ]; then
sk=${arg#-sk}
elif [ \( "_${arg#-rt}" != "_$arg" \) -a \( -z "$file" \) ]; then
rt=${arg#-rt}
elif [ \( "_${arg#-kd}" != "_$arg" \) -a \( -z "$file" \) ]; then
kd=${arg#-kd}
elif [ \( "_${arg#-lp}" != "_$arg" \) -a \( -z "$file" \) ]; then
lp=${arg#-lp}
elif [ \( "_${arg#-ls}" != "_$arg" \) -a \( -z "$file" \) ]; then
ls=${arg#-ls}
elif [ \( "_${arg#-fn}" != "_$arg" \) -a \( -z "$file" \) -a \
-n "$(echo -n "_${arg#-fn}" | grep '^_[0-9]\{1,\}$')" ]; then
fn=${arg#-fn}
fn=$((fn+0))
elif [ \( "_$arg" = '_-li' \) -a \( -z "$file" \) ]; then
unoptli=''
elif [ \( "_$arg" = '_--xpath' \) -a \( -z "$file" \) ]; then
rt=''
kd='/'
lp='['
ls=']'
fn=1
unoptli=''
elif [ \( "_$arg" = '_-t' \) -a \( -z "$file" \) ]; then
optt='#'
unoptt=''
elif [ \( \( -f "$arg" \) -o \( -c "$arg" \) \) -a \( -z "$file" \) ]; then
file=$arg
elif [ \( "_$arg" = "_-" \) -a \( -z "$file" \) ]; then
file='-'
else
cat <<____USAGE 1>&2
Usage : ${0##*/} [JSON_file] ←JSONPath表現
: ${0##*/} --xpath [JSON_file] ←XPath表現
: ${0##*/} [2letters_options...] [JSON_file] ←カスタム表現
Options : -sk<s> はキー名文字列内にあるスペースの置換文字列(デフォルトは"_")
: -rt<s> はルート階層シンボル文字列指定(デフォルトは"$")
: -kd<s> は各階層のキー名文字列間のデリミター指定(デフォルトは".")
: -lp<s> は配列キーのプレフィックス文字列指定(デフォルトは"[")
: -ls<s> は配列キーのサフィックス文字列指定(デフォルトは"]")
: -fn<n> は配列キー番号の開始番号(デフォルトは0)
: -li は配列行終了時に添字なしの配列フルパス行(値は空)を挿入する
: --xpathは階層表現をXPathにする(-rt -kd/ -lp[ -ls] -fn1 -liと等価)
: -t は、値の型を区別する(文字列はダブルクォーテーションで囲む)
____USAGE
exit 1
fi
done
sk=$(echo -n "_$sk" |
od -A n -t o1 |
tr -d '\n' |
sed 's/^[[:blank:]]*137//' |
sed 's/[[:blank:]]*$//' |
sed 's/[[:blank:]]\{1,\}/\\/g')
rt=$(echo -n "_$rt" |
od -A n -t o1 |
tr -d '\n' |
sed 's/^[[:blank:]]*137//' |
sed 's/[[:blank:]]*$//' |
sed 's/[[:blank:]]\{1,\}/\\/g')
kd=$(echo -n "_$kd" |
od -A n -t o1 |
tr -d '\n' |
sed 's/^[[:blank:]]*137//' |
sed 's/[[:blank:]]*$//' |
sed 's/[[:blank:]]\{1,\}/\\/g')
lp=$(echo -n "_$lp" |
od -A n -t o1 |
tr -d '\n' |
sed 's/^[[:blank:]]*137//' |
sed 's/[[:blank:]]*$//' |
sed 's/[[:blank:]]\{1,\}/\\/g')
ls=$(echo -n "_$ls" |
od -A n -t o1 |
tr -d '\n' |
sed 's/^[[:blank:]]*137//' |
sed 's/[[:blank:]]*$//' |
sed 's/[[:blank:]]\{1,\}/\\/g')
[ -z "$file" ] && file='-'
# === データの流し込み ============================================= #
cat "$file" |
# #
# === 値としてのダブルクォーテーション(DQ)をエスケープしつつ ======= #
# ダブルクォーテーションで囲まれた"~"区間を単独行にする #
tr '"' '\n' |
awk ' \
BEGIN{ \
OFS=""; ORS=""; \
LF = sprintf("\n"); \
nextl = 0; # 次行の種類 0:記号行,1:データ行(最初),2:データ行(続き) \
while(getline line) { \
if (nextl == 0) { \
print line, LF; \
nextl = 1; \
} else if (nextl == 1) { \
if (! match(line,/^(\\\\)*\\$|[^\\](\\\\)*\\$/)) { \
# 行末に奇数個の\がなかった(→データとしてのDQはなかった場合)\
print "\"", line, "\"", LF; \
nextl = 0; \
} else { \
# 行末に奇数個の\があった(→データとしてのDQがあった場合) \
print "\"", substr(line,1,length(line)-1), "'$DQ'"; \
nextl = 2; \
} \
} else { \
if (! match(line,/^(\\\\)*\\$|[^\\](\\\\)*\\$/)) { \
# 行末に奇数個の\がなかった(→データとしてのDQはなかった場合)\
print line, "\"", LF; \
nextl = 0; \
} else { \
# 行末に奇数個の\があった(→データとしてのDQがあった場合) \
print substr(line,1,length(line)-1), "'$DQ'"; \
nextl = 2; \
} \
} \
} \
} \
' |
# #
# === DQ始まり以外の行の"{","}","[","]",":",","の前後に改行を挿入 == #
sed "/^[^\"]/s/\([][{}:,]\)/$LF\1$LF/g" |
# #
# === 無駄な空行は予め取り除いておく =============================== #
grep -v '^[[:blank:]]*$' |
# #
# === 行頭の記号を見ながら状態遷移させて処理(*1,strict版*2) ======== #
# (*1 エスケープしたDQもここで元に戻す) #
# (*2 JSONの厳密なチェックを省略するならもっと簡素で高速にできる) #
awk ' \
BEGIN { \
# キー文字列内にあるスペースの置換文字列をシェル変数に基づいて定義 \
alt_spc_in_key=sprintf("'"$sk"'"); \
# 階層表現文字列をシェル変数に基づいて定義する \
root_symbol=sprintf("'"$rt"'"); \
key_delimit=sprintf("'"$kd"'"); \
list_prefix=sprintf("'"$lp"'"); \
list_suffix=sprintf("'"$ls"'"); \
# データ種別スタックの初期化 \
datacat_stack[0]=""; \
delete datacat_stack[0] \
# キー名スタックの初期化 \
keyname_stack[0]=""; \
delete keyname_stack[0] \
# スタックの深さを0に設定 \
stack_depth=0; \
# エラー終了検出変数を初期化 \
_assert_exit=0; \
# 同期信号キャラクタ(事前にエスケープしていたDQを元に戻すため) \
DQ=sprintf("\026"); \
# 改行キャラクター \
LF =sprintf("\n"); \
# print文の自動フィールドセパレーター挿入と文末自動改行をなくす \
OFS=""; \
ORS=""; \
} \
# "{"行の場合 \
$0~/^{$/{ \
# データ種別スタックが空、又は最上位が"l0:配列(初期要素値待ち)"、 \
# "l1:配列(値待ち)"、"h2:ハッシュ(値待ち)"であることを確認したら \
# データ種別スタックに"h0:ハッシュ(キー未取得)"をpush \
if ((stack_depth==0) || \
(datacat_stack[stack_depth]=="l0") || \
(datacat_stack[stack_depth]=="l1") || \
(datacat_stack[stack_depth]=="h2") ) { \
stack_depth++; \
datacat_stack[stack_depth]="h0"; \
next; \
} else { \
_assert_exit=1; \
exit _assert_exit; \
} \
} \
# "}"行の場合 \
$0~/^}$/{ \
# データ種別スタックが空でなく最上位が"h0:ハッシュ(キー未取得)"、 \
# "h3:ハッシュ(値取得済)"であることを確認したら \
# データ種別スタック、キー名スタック双方をpop \
# もしpop直後の最上位が"l0:配列(初期要素値待ち)"または \
# "l1:配列(値待ち)"だった場合には"l2:配列(値取得直後)"に変更 \
# 同様に"h2:ハッシュ(値待ち)"だった時は"h3:ハッシュ(値取得済)"に \
if ((stack_depth>0) && \
((datacat_stack[stack_depth]=="h0") || \
(datacat_stack[stack_depth]=="h3") ) ) { \
delete datacat_stack[stack_depth]; \
delete keyname_stack[stack_depth]; \
stack_depth--; \
if (stack_depth>0) { \
if ((datacat_stack[stack_depth]=="l0") || \
(datacat_stack[stack_depth]=="l1") ) { \
datacat_stack[stack_depth]="l2" \
} else if (datacat_stack[stack_depth]=="h2") { \
datacat_stack[stack_depth]="h3" \
} \
} \
next; \
} else { \
_assert_exit=1; \
exit _assert_exit; \
} \
} \
# "["行の場合 \
$0~/^\[$/{ \
# データ種別スタックが空、又は最上位が"l0:配列(初期要素値待ち)"、 \
# "l1:配列(値待ち)"、"h2:ハッシュ(値待ち)"であることを確認したら \
# データ種別スタックに"l0:配列(初期要素値待ち)"をpush、 \
# およびキー名スタックに配列番号0をpush \
if ((stack_depth==0) || \
(datacat_stack[stack_depth]=="l0") || \
(datacat_stack[stack_depth]=="l1") || \
(datacat_stack[stack_depth]=="h2") ) { \
stack_depth++; \
datacat_stack[stack_depth]="l0"; \
keyname_stack[stack_depth]='"$fn"'; \
next; \
} else { \
_assert_exit=1; \
exit _assert_exit; \
} \
} \
# "]"行の場合 \
$0~/^\]$/{ \
# データ種別スタックが空でなく最上位が"l0:配列(初期要素値待ち)"、 \
# "l2:配列(値取得直後)"であることを確認したら \
# データ種別スタック、キー名スタック双方をpop \
# もしpop直後の最上位が"l0:配列(初期要素値待ち)"または \
# "l1:配列(値待ち)"だった場合には"l2:配列(値取得直後)"に変更 \
# 同様に"h2:ハッシュ(値待ち)"だった時は"h3:ハッシュ(値取得済)"に \
if ((stack_depth>0) && \
((datacat_stack[stack_depth]=="l0") || \
(datacat_stack[stack_depth]=="l2") ) ) { \
'"$unoptli"'print_keys_and_value(""); \
delete datacat_stack[stack_depth]; \
delete keyname_stack[stack_depth]; \
stack_depth--; \
if (stack_depth>0) { \
if ((datacat_stack[stack_depth]=="l0") || \
(datacat_stack[stack_depth]=="l1") ) { \
datacat_stack[stack_depth]="l2" \
} else if (datacat_stack[stack_depth]=="h2") { \
datacat_stack[stack_depth]="h3" \
} \
} \
next; \
} else { \
_assert_exit=1; \
exit _assert_exit; \
} \
} \
# ":"行の場合 \
$0~/^:$/{ \
# データ種別スタックが空でなく \
# 最上位が"h1:ハッシュ(キー取得済)"であることを確認したら \
# データ種別スタック最上位を"h2:ハッシュ(値待ち)"に変更 \
if ((stack_depth>0) && \
(datacat_stack[stack_depth]=="h1") ) { \
datacat_stack[stack_depth]="h2"; \
next; \
} else { \
_assert_exit=1; \
exit _assert_exit; \
} \
} \
# ","行の場合 \
$0~/^,$/{ \
# 1)データ種別スタックが空でないことを確認 \
if (stack_depth==0) { \
_assert_exit=1; \
exit _assert_exit; \
} \
'"$unoptli"'# 1.5)-liオプション有効時の動作 \
'"$unoptli"'if (substr(datacat_stack[stack_depth],1,1)=="l") { \
'"$unoptli"' print_keys_and_value(""); \
'"$unoptli"'} \
# 2)データ種別スタック最上位値によって分岐 \
# 2a)"l2:配列(値取得直後)"の場合 \
if (datacat_stack[stack_depth]=="l2") { \
# 2a-1)データ種別スタック最上位を"l1:配列(値待ち)"に変更 \
datacat_stack[stack_depth]="l1"; \
# 2a-2)キー名スタックに入っている配列番号を+1 \
keyname_stack[stack_depth]++; \
next; \
# 2b)"h3:ハッシュ(値取得済)"の場合 \
} else if (datacat_stack[stack_depth]=="h3") { \
# 2b-1)データ種別スタック最上位を"h0:ハッシュ(キー未取得)"に変更 \
datacat_stack[stack_depth]="h0"; \
next; \
# 2c)その他の場合 \
} else { \
# 2c-1)エラー \
_assert_exit=1; \
exit _assert_exit; \
} \
} \
# それ以外の行(値の入っている行)の場合 \
{ \
# 1)データ種別スタックが空でないことを確認 \
if (stack_depth==0) { \
_assert_exit=1; \
exit _assert_exit; \
} \
# 2)DQ囲みになっている場合は予めそれを除去しておく \
'"$optt"'value=(match($0,/^".*"$/))?substr($0,2,RLENGTH-2):$0; \
'"$unoptt"'value=$0; \
# 3)事前にエスケープしていたDQをここで元に戻す \
gsub(DQ,"\\\"",value); \
# 4)データ種別スタック最上位値によって分岐 \
# 4a)"l0:配列(初期要素値待ち)"又は"l1:配列(値待ち)"の場合 \
if ((datacat_stack[stack_depth]=="l0") || \
(datacat_stack[stack_depth]=="l1") ) { \
# 4a-1)キー名スタックと値を表示 \
print_keys_and_value(value); \
# 4a-2)データ種別スタック最上位を"l2:配列(値取得直後)"に変更 \
datacat_stack[stack_depth]="l2"; \
# 4b)"h0:ハッシュ(キー未取得)"の場合 \
} else if (datacat_stack[stack_depth]=="h0") { \
# 4b-1)値をキー名としてキー名スタックにpush \
gsub(/ /,alt_spc_in_key,value); \
keyname_stack[stack_depth]=value; \
# 4b-2)データ種別スタック最上位を"h1:ハッシュ(キー取得済)"に変更 \
datacat_stack[stack_depth]="h1"; \
# 4c)"h2:ハッシュ(値待ち)"の場合 \
} else if (datacat_stack[stack_depth]=="h2") { \
# 4c-1)キー名スタックと値を表示 \
print_keys_and_value(value); \
# 4a-2)データ種別スタック最上位を"h3:ハッシュ(値取得済)"に変更 \
datacat_stack[stack_depth]="h3"; \
# 4d)その他の場合 \
} else { \
# 4d-1)エラー \
_assert_exit=1; \
exit _assert_exit; \
} \
} \
# 最終処理 \
END { \
if (_assert_exit) { \
print "Invalid JSON format", LF > "/dev/stderr"; \
line1="keyname-stack:"; \
line2="datacat-stack:"; \
for (i=1;i<=stack_depth;i++) { \
line1=line1 sprintf("{%s}",keyname_stack[i]); \
line2=line2 sprintf("{%s}",datacat_stack[i]); \
} \
print line1, LF, line2, LF > "/dev/stderr"; \
} \
exit _assert_exit; \
} \
# キー名一覧と値を表示する関数 \
function print_keys_and_value(str) { \
print root_symbol; \
for (i=1;i<=stack_depth;i++) { \
if (substr(datacat_stack[i],1,1)=="l") { \
print list_prefix, keyname_stack[i], list_suffix; \
} else { \
print key_delimit, keyname_stack[i]; \
} \
} \
print " ", str, LF; \
} \
'
#! /bin/sh
#
# parsrx.sh
# XMLテキストから
# 階層インデックス付き値(tree indexed value)テキスへの正規化
# (例)
# <foo>
# あはは
# <bar hoge="ほげ" piyo="ぴよ">えへへ<br /><script></script></bar>
# いひひ
# </foo>
# ↓
# /foo/bar/@hoge ほげ
# /foo/bar/@piyo ぴよ
# /foo/bar/br
# /foo/bar/script
# /foo/bar えへへ
# /foo \n あはは\n \n いひひ\n
# ◇第1列はXMLパス名(XPath形式:区切りは"/"、ただし属性名手前には"@")
# ◇第2列(最初のスペース文字の次以降全部)はそのパスの持つ値(空値の場合あり)
# ◇よって grep '^foo/bar ' | sed 's/^[^ ]* //' などと
# 後ろに grep, sed をパイプで繋げれば目的のキーの値部分が取れる。
# さらにこれを sed 's/\\n[[:blank:]]*/\\n/g; s/\\n$//; s/^\\n//' 等に
# パイプすれば、前後の余計な改行やインデントを取り除け、さらに
# sed 's/\\n/\
# /g' などにパイプすれば、改行も元の姿に復元できる。
# ◇単独タグ(<br />など)の場合は、第1列の後にスペースが付かない。一方、
# 別途閉じタグがある(<script></script>など)の場合はスペースが付く。
# (注意)
# ・XMLに準拠していないもの(記号がヘン,タグが正しく入れ子になっていない等)を
# 与えられた場合は正常に正規化されることを保証できない
# ・HTMLも下記のような処理を予めして、XMLに準拠させれば扱える
# - 改行コードを\nにする(XMLの規約)
# - meta,link,br,img,input,hr,embed,area,base,col,keygen,param,source など
# 閉じタグの無いタグは単独で閉じさせる
#
# Usage : parsrx.sh [-c] [-n] [-lf<str>] [XML_file]
# Options : -c はタグ内に含まれる子タグの可視化
# -n は同親を持つその名前のタグの出現回数を、タグ名の後ろに付ける
# -lf は値として含まれている改行を表現する文字列指定(デフォルトは
# "\n"であり、この場合は元々の \ が \\ にエスケープされる)
#
# Written by Rich Mikan(richmikan[at]richlab.org) / Date : Sep 28, 2014
SCT=$(printf '\016') # タグ開始端(候補)エスケープ用文字
ECT=$(printf '\017') # タグ終端(候補)エスケープ用文字
PRO=$(printf '\020') # 属性行開始識別文字
SCS=$(printf '\021') # 一重引用符開始端(候補)エスケープ用文字
ECS=$(printf '\022') # 一重引用符終端(候補)エスケープ用文字
SCD=$(printf '\023') # 二重引用符開始端(候補)エスケープ用文字
ECD=$(printf '\024') # 二重引用符終端(候補)エスケープ用文字
SPC=$(printf '\025') # 引用符内スペースのエスケープ用文字
TAB=$(printf '\026') # 引用符内タブのエスケープ用文字
GT=$( printf '\027') # 引用符内">"のエスケープ用文字
LT=$( printf '\030') # 引用符内"<"のエスケープ用文字
SLS=$(printf '\031') # 引用符内"/"のエスケープ用文字
LF=$( printf '\177') # 改行(タグ内の引用符外は除く)のエスケープ用文字
T=$( printf '\011') # タブ(エスケープ用ではない)
N=$( printf '\\\012_');N=${N%_} # sedコマンド用の改行(エスケープ用ではない)
# 配列にlength()が使えない旧来のAWKであれば独自の関数を用いる
if awk 'BEGIN{a[1]=1;b=length(a)}' 2>/dev/null; then
arlen='length'
else
arlen='arlen'
fi
optlf=''
bsesc='\\'
unoptc='#'
unoptn='#'
file=''
printhelp=0
for arg in "$@"; do
if [ \( "_${arg#-lf}" != "_$arg" \) -a \( -z "$file" \) ]; then
optlf=$(printf '%s' "${arg#-lf}_" |
tr -d '\n' |
sed 's/\([\&/]\)/\\\1/g' )
optlf=${optlf%_}
elif [ \( "_${arg#-}" != "_$arg" \) -a \( -n "_${arg#-}" \) \
-a \( -z "$file" \) ]
then
for opt in $(echo "_${arg#-}" | sed 's/^_//;s/\(.\)/\1 /g'); do
case "$opt" in
c) # -cオプションが付いた場合、一番最後のAWKのコードを一部有効にする
unoptc=''
;;
n) # -nオプションが付いた場合、一番最後のAWKのコードを一部有効にする
unoptn=''
;;
*)
printhelp=1
;;
esac
done
elif [ \( "_$arg" = '_-' \) -a \( -z "$file" \) ]; then
file='-'
elif [ \( \( -f "$arg" \) -o \( -c "$arg" \) \) -a \( -z "$file" \) ]; then
file=$arg
else
printhelp=1;
fi
done
if [ $printhelp -ne 0 ]; then
cat <<__USAGE 1>&2
Usage : ${0##*/} [-c] [-n] [-lf<str>] [XML_file]
Options : -c はタグ内に含まれる子タグの可視化
-n は同親を持つその名前のタグの出現回数を、タグ名の後ろに付ける
-lf は値として含まれている改行を表現する文字列指定(デフォルトは"\n")
__USAGE
exit 1
fi
[ -z "$optlf" ] && { optlf='\\n'; bsesc='\\\\'; }
[ -z "$file" ] && file='-'
# === データの流し込み ======================================================= #
cat "$file" |
# #
# === タグ内の属性値に含まれるスペース,改行,"<",">"を全てエスケープする ====== #
# 1)元あった改行に印をつける #
sed 's/$/'"$LF"'/' |
# 2)一般タグ(それ以外も混ざる)の始まる前で改行 #
sed 's/\(<[^'" $T"'!-.0-9;-@[-^`{-~][^'" $T"'!-,/;-@[-^`{-~]*\)/'"$N$SCT"'\1/g'|
# 3)属性値開始括弧(それ以外も混ざる)手前で改行 #
sed 's/='"'"'/='"$N$SCS""'"'/g' |
sed 's/="/='"$N$SCD"'"/g' |
# 4)属性値終了括弧(それ以外も混ざる)前で改行 #
sed 's/\([^'"$SCS$SCD"']\)'"'"'\(['" $T$LF"'/>]\)/\1'"$N$ECS""'"'\2/g' |
sed 's/\([^'"$SCS$SCD"']\)"\(['" $T$LF"'/>]\)/\1'"$N$ECD"'"\2/g' |
sed 's/^'"'"'\(['" $T$LF"'/>]\)/'"$ECS'"'\1/' |
sed 's/^"\(['" $T$LF"'/>]\)/'"$ECD"'"\1/' |
sed "s/^'$/$ECS'/" |
sed 's/^"$/'"$ECD"'"/' |
# 5)一般タグ(それ以外も混ざる)の終わる前で改行 #
sed 's/>/'"$N$ECT"'>/g' |
# 6)本処理(混ざりを取り除く) #
# ・タグ内の属性値区間のスペース,タブ,"<",">"をエスケープ #
# ・エスケープした改行でもタグ内かつ引用符外のものは半角スペースに変換 #
# ・一重引用符と二重引用符(値としてのものを除く)はここで除去 #
awk ' \
BEGIN { \
OFS = ""; \
ORS = ""; \
LF = sprintf("\n"); \
Sct = "'"$SCT"'"; # タグ開始端候補識別子として使う文字........残す \
Ect = "'"$ECT"'"; # タグ終了端候補識別子として使う文字........残す \
Scs = "'"$SCS"'"; # 一重引用符開始端候補識別子として使う文字..消す \
Ecs = "'"$ECS"'"; # 一重引用符終了端候補識別子として使う文字..消す \
Scd = "'"$SCD"'"; # 二重引用符開始端候補識別子として使う文字..消す \
Ecd = "'"$ECD"'"; # 二重引用符終了端候補識別子として使う文字..消す \
SPC = "'"$SPC"'"; # スペースをエスケープするための文字........これに置換 \
TAB = "'"$TAB"'"; # タブをエスケープするための文字............これに置換 \
SLS = "'"$SLS"'"; # /をエスケープするための文字...............これに置換 \
GT = "'"$GT"'"; # >をエスケープするための文字(引用符内用)...これに置換 \
LT = "'"$LT"'"; # <をエスケープするための文字(引用符内用)...これに置換 \
in_tag = 0; # 今読み進めた最後の文字位置はタグ内か \
in_quot = 0; # 今読み進めた最後の文字位置は括弧内か(SQなら1,DQなら2) \
while (getline line) { \
headofline = substr(line,1,1); \
if (in_tag == 0) { \
# 1.タグ外だった場合 \
if ( headofline == Sct) { \
# 1-1.タグ開始端に来た場合 \
in_tag = 1; \
gsub(/'"$LF"'/, " ", line); \
print LF, line; \
} else { \
# 1-2.タグに来てない場合 \
print substr(line,2); \
} \
} else if (in_quot == 0) { \
# 2.タグ内だけど引用符外だった場合 \
if ( headofline == Ect) { \
# 2-1.タグ終端に来た場合 \
in_tag = 0; \
print line, LF; \
} else if (headofline == Scs) { \
# 2-2.一重引用符開始端に来た場合 \
in_quot = 1; \
gsub(/ / ,SPC, line); \
gsub(/\t/,TAB, line); \
gsub(/\//,SLS, line); \
gsub(/>/ , GT, line); \
gsub(/</ , LT, line); \
print substr(line,3); \
} else if (headofline == Scd) { \
# 2-3.二重引用符開始端に来た場合 \
in_quot = 2; \
gsub(/ / ,SPC, line); \
gsub(/\t/,TAB, line); \
gsub(/\//,SLS, line); \
gsub(/>/ , GT, line); \
gsub(/</ , LT, line); \
print substr(line,3); \
} else { \
# 2-4.その他(タグ内で属性括弧外のまま)の場合 \
gsub(/'"$LF"'/, " ", line); \
print substr(line,2); \
} \
} else if (in_quot == 1) { \
# 3.一重引用符内だった場合 \
if ( headofline == Ecs) { \
# 3-1.一重引用符終端に来た場合 \
in_quot = 0; \
gsub(/'"$LF"'/, " ", line); \
print substr(line,3); \
} else { \
# 3-2.その他(タグ開始端や上記を除く引用符端....ここでは来ないはず) \
gsub(/ / ,SPC, line); \
gsub(/\t/,TAB, line); \
gsub(/\//,SLS, line); \
gsub(/>/ , GT, line); \
gsub(/</ , LT, line); \
print line; \
} \
} else { \
# 4.二重引用符内だった場合 \
if ( headofline == Ecd) { \
# 4-1.二重引用符終端に来た場合 \
in_quot = 0; \
gsub(/'"$LF"'/, " ", line); \
print substr(line,3); \
} else { \
# 4-2.その他(タグ開始端や上記を除く引用符端....ここでは来ないはず) \
gsub(/ / ,SPC, line); \
gsub(/\t/,TAB, line); \
gsub(/\//,SLS, line); \
gsub(/>/ , GT, line); \
gsub(/</ , LT, line); \
print line; \
} \
} \
} \
} \
' |
# #
# === コメント(<!-- -->)を削除する =========================================== #
tr -d '\n' |
sed 's/<!--/'"$N"'<!--'"$N"'/g' |
sed 's/-->/-->'"$N"'/g' |
sed '/^<!--/,/-->$/d' |
tr -d '\n' |
# #
# === タグ名行、属性行、タグ内文字列行の3種類に行を分離する ================== #
# 1)タグ文字列部分を単独の行にする(同時に"<",">"はトル) #
sed 's/'"$SCT"'<\([^'"$ECT"']*\)'"$ECT"'>/'"$N$SCT"'\1'"$N"'/g' |
# 2)タグの名称部分と各属性部分を1つ1つ個別の行にする #
# ・先頭にタグ行or属性行識別子をつけて #
# ・属性行を先にし、タグ行は最後にする #
awk ' \
# the alternative length function for array variable \
function arlen(ar,i,l){for(i in ar){l++;}return l;} \
\
BEGIN { \
OFS = ""; \
Tag = "'"$SCT"'"; # タグ行識別子として使う文字..残す \
Pro = "'"$PRO"'"; # 属性行識別子として使う文字..追加する \
while (getline line) { \
headofline = substr(line,1,1); \
if (headofline == Tag) { \
# 1.タグ行である場合.... \
split(line, items); \
tagname = substr(items[1],2); \
sub(/\/$/, "", tagname); \
# 1-1.単独タグかどうかを検出 \
i = '$arlen'(items); \
if (match(items[i],/\/$/)) { \
singletag = 1; \
if (RSTART == 1) { \
i--; \
} else { \
items[i] = substr(items[i], 1, RSTART-1); \
} \
} else { \
singletag = 0; \
} \
# 1-2.各属性を各々単独の行として出力 \
for (j=2; j<=i; j++) { \
item = items[j]; \
if (match(item, /^[^=]+/)) { \
proname = substr(item,1,RLENGTH); \
if (match(item, /^[^=]+["'"'"'].+["'"'"']$/)) { \
k = length(proname); \
proval = substr(item,k+3,length(item)-k-3); \
print Pro, tagname, Pro, proname, " ", proval; \
} else if (length(proname) == length(item)) { \
print Pro, tagname, Pro, proname, " "; \
} else { \
proval = substr(item,length(proname)+2); \
print Pro, tagname, Pro, proname, " ", proval; \
} \
} \
} \
# 1-3.タグ名を単独の行として出力 \
print Tag, tagname; \
# 1-4.単独タグだった場合は閉じタグ行を追加 \
if (singletag) { \
print Tag, "//", tagname; # (後で区別できるように"//"とする) \
} \
} else { \
# 2.タグ行ではない場合....、そのまま出力 \
print line; \
} \
} \
} \
' |
# === タグ,属性を絶対パス化し、タグ内文字列をタグの値として同行に記す ======== #
# ・次のような形式になる(第1列はXPath形式) #
# /PATH/TO/TAG_NAME VALUE #
# /PATH/TO/TAG_NNAME/@PROPERTY_NAME VALUE #
# ・VALUEが空の場合でも手前に半角スペースが1個入る #
awk ' \
BEGIN { \
OFS = ""; \
ORS = ""; \
LF = sprintf("\n"); \
Tag = "'"$SCT"'"; # タグ行識別子として使う文字..消す \
Pro = "'"$PRO"'"; # 属性行識別子として使う文字..消す \
split("", tagpath); # K:階層番号、V:パス名 \
split("", tagvals); # (a)K:階層深度 V:要素数、(b)K:深度,番号 V:文字列 \
split("", tagbros); # K:階層番号、V:"/所属タグの名前/名前/名前/…" \
split("", tagrept); # K:"階層番号/タグ名"、V:出現回数 \
currentdepth = 0; # 現在いる階層の深度 \
currentpathitems = 0; # 現在のフルパスが持っている文字列の個数 \
while (getline line) { \
headofline = substr(line,1,1); \
if ( headofline == Tag) { \
# 1.タグ行だった場合 \
if (substr(line,2,1) == "/") { \
# 1-1.タグ終了行だった場合 \
# 現在の階層の値を付けながら値を表示 \
# 一階層出る \
for (i=1; i<=currentdepth; i++) { \
s = tagpath[i]; \
print "/", s; \
'"$unoptn"'print "[", tagrept[i "/" s], "]"; \
} \
if (substr(line,3,1) != "/") {print " ";} #←単独タグだった場合は、 \
for (i=1; i<=currentpathitems; i++) { # タグパスの後ろに \
print tagvals[currentdepth "," i]; # " "をつけない。 \
delete tagvals[currentdepth "," i]; \
} \
print LF; \
delete tagpath[currentdepth]; \
'"$unoptn"'i = currentdepth + 1; \
'"$unoptn"'if (i in tagbros) { \
'"$unoptn"' split(substr(tagbros[i],2), array, "/"); \
'"$unoptn"' for (j in array) { \
'"$unoptn"' delete tagrept[i "/" array[j]]; \
'"$unoptn"' } \
'"$unoptn"' split("", array); \
'"$unoptn"'} \
currentdepth--; \
currentpathitems = tagvals[currentdepth]; \
delete tagvals[currentdepth]; \
} else { \
# 1-2.タグ開始行だった場合 \
# 一階層入る \
currenttagname = substr(line,2); \
'"$unoptc"'childtag = "<" currenttagname "/>"; \
'"$unoptc"'currentpathitems++; \
'"$unoptc"'tagvals[currentdepth "," currentpathitems] = childtag; \
tagvals[currentdepth] = currentpathitems; \
currentpathitems = 0; \
currentdepth++; \
tagpath[currentdepth] = currenttagname; \
'"$unoptn"'if (currentdepth in tagbros) { \
'"$unoptn"' if (currentdepth "/" currenttagname in tagrept) { \
'"$unoptn"' tagrept[currentdepth "/" currenttagname]++; \
'"$unoptn"' } else { \
'"$unoptn"' s = tagbros[currentdepth] "/" currenttagname; \
'"$unoptn"' tagbros[currentdepth] = s; \
'"$unoptn"' tagrept[currentdepth "/" currenttagname] = 1; \
'"$unoptn"' } \
'"$unoptn"'} else { \
'"$unoptn"' tagbros[currentdepth] = "/" currenttagname; \
'"$unoptn"' tagrept[currentdepth "/" currenttagname] = 1; \
'"$unoptn"'} \
} \
} else if (headofline == Pro) { \
# 2.属性行だった場合 \
for (i=1; i<=currentdepth; i++) { \
s = tagpath[i]; \
print "/", s; \
'"$unoptn"'print "[", tagrept[i "/" s], "]"; \
} \
s = substr(line,2); \
i = index(s, "'"$PRO"'"); \
currenttagname = substr(s, 1, i-1); \
print "/", currenttagname; \
'"$unoptn"'j = currentdepth + 1; \
'"$unoptn"'if ((j "/" currenttagname) in tagrept) { \
'"$unoptn"' print "[", (tagrept[j "/" currenttagname]+1), "]"; \
'"$unoptn"'} else { \
'"$unoptn"' print "[1]"; \
'"$unoptn"'} \
print "/@", substr(s,i+1), LF; \
} else { \
# 3.その他の行だった場合 \
# 現在の階層の値変数にその行を追加 \
currentpathitems++; \
tagvals[currentdepth "," currentpathitems] = line; \
} \
} \
} \
' |
# #
# === アンエスケープ ========================================================= #
# 1)スペース,タブ,"<",">","/"を元に戻す #
sed 's/'"$GT"'/>/g' |
sed 's/'"$LT"'/</g' |
sed 's/'"$SLS"'/\//g' |
sed 's/'"$SPC"'/ /g' |
sed 's/'"$TAB"'/'"$T"'/g' |
# 2)エスケープ改行を、引数で指定された(あるいはデフォルトの)文字列に変換する #
if [ "_$bsesc" != '_\\' ]; then #
sed 's/\\/'"$bsesc"'/g' #
else #
cat #
fi |
sed 's/'"$LF"'/'"$optlf"'/g'
#! /bin/sh
#
# unsecj.sh
# JSONによるエスケープ文字を含む文字列をアンエスケープする
# ・Unicodeエスケープが含まれている場合はその部分をUTF-8化する
# ・このスクリプトはJSONの値部分のみの処理を想定している
# (値の中に改行を示すエスケープがあったら素直に改行に変換する。
# これが困る場合は -n オプションを使う。すると "\r" と"\n" の
# まま出力される)
# ・JSON文字列のパース(キーと値の分離)はparsrj.shで予め行うこと
#
# Usage: unsecj.sh [-n] [JSON_value_textfile]
#
# Written by Rich Mikan(richmikan[at]richlab.org) / Date : Feb 3, 2014
BS=$(printf '\010') # バックスペース
TAB=$(printf '\011') # タブ
LF=$(printf '\\\n_');LF=${LF%_} # 改行(sedコマンド取扱用)
FF=$(printf '\014') # 改ページ
CR=$(printf '\015') # キャリッジリターン
if [ \( $# -ge 1 \) -a \( "_$1" = '_-n' \) ]; then
LF_NONDECODE=1
shift
fi
if [ \( $# -eq 1 \) -a \( \( -f "$1" \) -o \( -c "$1" \) \) ]; then
file=$1
elif [ \( $# -eq 0 \) -o \( \( $# -eq 1 \) -a \( "_$1" = '_-' \) \) ]
then
file='-'
else
echo "Usage : ${0##*/} [-n] [JSON_value_textfile]" 1>&2
exit 1
fi
# === データの流し込み ============================================= #
cat "$file" |
# #
# === もとからあった改行に印"\n"をつける =========================== #
if [ -z "${LF_NONDECODE:-}" ]; then #
sed '$!s/$/\\n/' #
else #
sed '$!s/$/\\N/' # -nオプション付の場合は"\n"を保持する為"\N"とする#
fi |
# #
# === Unicodeエスケープ文字列(\uXXXX)の手前に改行を挿入 ============ #
sed 's/\(\\u[0-9A-Fa-f]\{4\}\)/'"$LF"'\1/g' |
# #
# === Unicodeエスケープ文字列をデコード ============================ #
# (但し\u000a と \u000d と \u005c は \n、\r、\\ に変換する) #
awk ' \
BEGIN { \
OFS=""; ORS=""; \
for(i=255;i>=0;i--) { \
s=sprintf("%c",i); \
bhex2chr[sprintf("%02x",i)]=s; \
bhex2chr[sprintf("%02X",i)]=s; \
#bhex2int[sprintf("%02x",i)]=i; \
#bhex2int[sprintf("%02X",i)]=i; \
} \
for(i=65535;i>=0;i--) { # 0000~FFFFの16進値を10進値に変 \
whex2int[sprintf("%02x",i)]=i; # 換する際、00~FFまでの連想配列 \
whex2int[sprintf("%02X",i)]=i; # 256個を作って2桁ずつ2度使うより \
} # こちらを1度使う方が若干速かった \
} \
/^\\u000[Aa]/ { \
print "\\n", substr($0,7); \
next; \
} \
/^\\u000[Dd]/ { \
print "\\r", substr($0,7); \
next; \
} \
/^\\u005[Cc]/ { \
print "\\\\", substr($0,7); \
next; \
} \
/^\\u00[0-7][0-9a-fA-F]/ { \
print bhex2chr[substr($0,5,2)], substr($0,7); \
next; \
} \
/^\\u0[0-7][0-9a-fA-F][0-9a-fA-F]/ { \
i=whex2int[substr($0,3,4)]; \
#i=bhex2int[substr($0,3,2)]*256+bhex2int[substr($0,5,2)]; \
printf("%c",192+int(i/64)); \
printf("%c",128+ i%64 ); \
print substr($0,7); \
next; \
} \
/^\\u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]/ { \
i=whex2int[substr($0,3,4)]; \
#i=bhex2int[substr($0,3,2)]*256+bhex2int[substr($0,5,2)]; \
printf("%c",224+int( i/4096 )); \
printf("%c",128+int((i%4096)/64)); \
printf("%c",128+ i%64 ); \
print substr($0,7); \
next; \
} \
{ \
print; \
} \
' |
# === \\ 以外のエスケープ文字列をデコード ========================== #
sed 's/\\"/"/g' |
sed 's/\\\//\//g' |
sed 's/\\b/'"$BS"'/g' |
sed 's/\\f/'"$FF"'/g' |
if [ -z "${LF_NONDECODE:-}" ]; then #
sed 's/\\r/'"$CR"'/g' | sed 's/\\n/'"$LF"'/g' #
else #
sed 's/\\N/'"$LF"'/g' #
fi |
sed 's/\\t/'"$TAB"'/g' |
# #
# === \\ をデコード ================================================ #
sed 's/\\\\/\\/g'
#! /bin/sh
#
# xpathread.sh
# XPath形式インデックス付きデータをタグ形式に変換する
# (例)
# /foo/bar/onamae かえる
# /foo/bar/nenrei 3
# /foo/bar
# /foo/bar/onamae ひよこ
# /foo/bar/nenrei 1
# /foo/bar
# /foo
# ↓
# ★ xpath2tag.sh /foo/bar <上記データファイル> として実行すると...
# ↓
# onamae nenrei
# かえる 3
# ひよこ 1
# (備考)
# ・XMLデータは一旦parsrx.shに掛けることでXPath形式インデックス付きデータに
# になる
# ・JSONデータは一旦parsrj.shに--xpathオプション付きで掛けることでXPath形式
# インデックス付きデータになる
# ・処理対象データにおいて、対象となる階層名に、添字[n]が付いていてもよい。
# (上記の例では、最初の3行が /foo/bar[1]...、後の3行が/foo/bar[2]...、と
# なっていてもよい。)
#
# Usage : xpathread.sh [-s<str>] [-n<str>] [-p] <XPath> [XPath_indexed_data]
# Options : -s is for setting the substitution of blank (default:"_")
# : -n is for setting the substitution of null (default:"@")
# : -p permits to add the properties of the tag to the table
#
# Written by Rich Mikan(richmikan[at]richlab.org) / Date : Jun 29, 2014
# ===== 配列にlength()が使えない旧来のAWKであれば独自の関数を用いる ==
if awk 'BEGIN{a[1]=1;b=length(a)}' 2>/dev/null; then
arlen='length'
else
arlen='arlen'
fi
# ===== 引数を解析する ===============================================
opts='_'
optn='@'
optp=''
xpath=''
xpath_file=''
optmode=''
i=0
printhelp=0
for arg in "$@"; do
i=$((i+1))
if [ -z "$optmode" ]; then
case "$arg" in
-[sdnip]*)
ret=$(echo "_${arg#-}" |
awk '{
opts = "_";
optn = "_";
optp = "_";
opt_str = "";
for (n=2; n<=length($0); n++) {
s = substr($0,n,1);
if ((s == "s") || (s == "d")) {
opts = "s";
opt_str = substr($0, n+1);
break;
} else if ((s == "n") || (s == "i")) {
optn = "n";
opt_str = substr($0, n+1);
break;
} else if (s == "p") {
optp = "p";
}
}
printf("%s%s%s %s", opts, optn, optp, opt_str);
}')
ret1=${ret%% *}
ret2=${ret#* }
if [ "${ret1#*s}" != "$ret1" ]; then
opts=$ret2
fi
if [ "${ret1#*n}" != "$ret1" ]; then
if [ -n "$ret2" ]; then
optn=$ret2
else
optmode='n'
fi
fi
if [ "${ret1#*p}" != "$ret1" ]; then
optp='#'
fi
;;
*)
if [ -z "$xpath" ]; then
if [ $i -lt $(($#-1)) ]; then
printhelp=1
break
fi
xpath=$arg
elif [ -z "$xpath_file" ]; then
if [ $i -ne $# ]; then
printhelp=1
break
fi
if [ \( ! -f "$xpath_file" \) -a \
\( ! -c "$xpath_file" \) -a \
\( ! "_$xpath_file" != '_-' \) ]
then
printhelp=1
break
fi
xpath_file=$arg
else
printhelp=1
break
fi
;;
esac
elif [ "$optmode" = 'n' ]; then
optn=$arg
optmode=''
else
printhelp=1
break
fi
done
[ -n "$xpath" ] || printhelp=1
if [ $printhelp -ne 0 ]; then
cat <<-__USAGE 1>&2
Usage : ${0##*/} [-s<str>] [-n<str>] [-p] <XPath> [XPath_indexed_data]
Options : -s is for setting the substitution of blank (default:"_")
: -n is for setting the substitution of null (default:"@")
: -p permits to add the properties of the tag to the table
__USAGE
exit 1
fi
[ -z "$xpath_file" ] && xpath_file='-'
# ===== テンポラリーファイルを確保する ===============================
which mktemp >/dev/null 2>&1 || {
mktemp_fileno=0
mktemp() {
local mktemp_filename
mktemp_filename="/tmp/${0##*/}.$$.$mktemp_fileno"
mktemp_fileno=$((mktemp_fileno+1))
touch "$mktemp_filename"
chmod 600 "$mktemp_filename"
echo "$mktemp_filename"
}
}
tempfile=$(mktemp -t "${0##*/}.XXXXXXXX")
if [ $? -eq 0 ]; then
trap "rm -f $tempfile; exit" EXIT HUP INT QUIT ALRM SEGV TERM
else
echo "${0##*/}: Can't create a temporary file" 1>&2
exit 1
fi
# ===== 下記の前処理を施したテキストをテンポラリーファイルに書き出す =
# ・指定されたパス自身とその子でない(=孫以降)の行は削除
# ・指定されたパス自身の行は"/"として出力
# ・第1列は子の名前のみとし、更に添字[n]があれば取り除く
# ・第2列の空白を全て所定の文字に置き換える
awk '
BEGIN {
xpath = "'"$xpath"'";
xpathlen = length(xpath);
if (substr(xpath,xpathlen) == "/") {
sub(/\/$/, "", xpath);
xpathlen--;
}
while (getline line) {
i = index(line, " ");
f1 = substr(line, 1, i-1);
if (substr(f1,1,xpathlen) != xpath) {
continue;
}
f1 = substr(f1, xpathlen+1);
sub(/^\[[0-9]+\]/, "", f1);
if (length(f1) == 0) {
print "/";
continue;
}
f1 = substr(f1, 2);
j = index(f1, "/");
if (j != 0) {
'"$optp"'continue;
if (substr(f1,j+1,1) != "@") {
continue;
}
}
sub(/\[[0-9]+\]$/, "", f1);
if ((i==0) || (i==length(line))) {
f2 = "";
} else {
f2 = substr(line, i+1);
gsub(/[[:blank:]]/, "'"$opts"'", f2);
}
print f1, f2;
}
}
' "$xpath_file" > "$tempfile"
# ===== 対象タグ名の一覧をスペース区切りで列挙する ===================
tags=$(awk ' \
BEGIN { \
OFS = ""; \
ORS = ""; \
split("", tagnames); \
split("", tags); \
numoftags = 0; \
while (getline line) { \
if (line == "/") { \
continue; \
} \
sub(/ .*$/, "", line); \
if (line in tagnames) { \
continue; \
} \
numoftags++; \
tagnames[line] = 1; \
tags[numoftags] = line; \
} \
if (numoftags > 0) { \
print tags[1]; \
} \
for (i=2; i<=numoftags; i++) { \
print " ", tags[i]; \
} \
} \
' "$tempfile" )
# ===== タグ表を生成する =============================================
awk -v tags="$tags" '
# the alternative length function for array variable
function arlen(ar,i,l){for(i in ar){l++;}return l;}
BEGIN {
# タグ名と出現順序を登録
split(tags, order2tag);
split("" , tag2order);
numoftags = '$arlen'(order2tag);
for (i=1; i<=numoftags; i++) {
tag2order[order2tag[i]] = i;
}
# その他初期設定
OFS = "";
ORS = "";
LF = sprintf("\n");
# 最初の行(タグ行)を出力
print tags, LF;
# データ行を出力
split("", fields);
while (getline line) {
if (line != "/") {
# a.通常の行(タグ名+値)なら値を保持
i = index(line, " ");
f1 = substr(line, 1 , i-1);
f2 = substr(line, i+1 );
if (length(f2)) {
fields[tag2order[f1]] = f2;
}
} else {
# b."/"行(一周した印)なら一行出力し、その行の保持データをクリア
if (numoftags >= 1) {
print (1 in fields) ? fields[1] : "'"$optn"'";
}
for (i=2; i<=numoftags; i++) {
print " ", (i in fields) ? fields[i] : "'"$optn"'";
}
print LF;
split("", fields);
continue;
}
}
}
' "$tempfile"
@colrichie
Copy link
Author

2012/08/04:
初版リリース

@colrichie
Copy link
Author

2012/08/17:
JSON版もリリース

@colrichie
Copy link
Author

2012/12/15:
名前変更、ヘルプの追加、/dev/std* を使わないコードへ代替、
及びjsonunesc.shを追加

@colrichie
Copy link
Author

2013/02/05:
ESCキャラクター(0x15)をSYN(0x16)に変更

@colrichie
Copy link
Author

2013/05/08:
(1)jsonparser.shの引数解析用の条件文で、一部括弧が多かった不具合を修正
(2)待望のxmlparser.sh初版をリリース (バグ修正 23:31JST)

@colrichie
Copy link
Author

2013/05/09:
xmlparser.shの第1列(XML絶対パス)をXPath形式にした
xmlparser.shに-cオプションを追加

そして、コマンド名をネイティブっぽくした。(ドキュメントは日本語だけど)
csvparser.sh→parsrc.sh
jsonparser.sh→parsrj.sh
xmlparser.sh→parsrx.sh
jsonunesc.sh→unescj.sh

@colrichie
Copy link
Author

2013/05/11:
parsrj.shの第1列(絶対パス表記)をJSONPath形式にした。
(ただしXPath表記などにも変更可能)

@colrichie
Copy link
Author

2013/05/12:
(1)parsrx.shの-cオプションのバグ修正と、-nオプションの追加、その他リファクタリングを行った
(2)parsrx.shに-liオプションの追加
(3)新規スクリプトxpathread.shの追加。これでXMLもJSONもタグ形式に変換することが可能となる

@colrichie
Copy link
Author

2013/05/25:
(1)parsrj.shに-skオプションを追加し、キー文字列内にスペースがあった場合はデフォルトで"_"に置換する仕様にした。
(2)xpathname.shの-dオプションと-iオプションを、各々-s、-nに変更。(変更前のも受け付けるようにはなっている)
(3)parsrc.shに-lfオプションを追加

@colrichie
Copy link
Author

2014/01/11:
unescj.shが、\uxxxxが同一行に複数あると失敗するというポカミスに今頃気付いて修正。

@colrichie
Copy link
Author

2014/02/03:
unescj.shを、JSONパスが付いたままの状態でも使えるよう、-nオプションを追加した。

@colrichie
Copy link
Author

2014/09/28:
(1)parsrc.shの-lfオプションの動作を、parsrx.shと同じにした。
(2)parsrj.shでダブルクォーテーションの手前に\があるデータの間違った解釈を是正した。
(3)parsrj.shで改行無しの巨大なJSONが与えられた場合でも(マルチコア環境で)高速動作するようにした。
(4)parsrx.shで巨大なXMLが与えられた場合でも(マルチコア環境で)高速動作するようにした。

@colrichie
Copy link
Author

2014/09/28:
THIS REPOSITORY WAS MOVED TO https://github.com/ShellShoccar-jpn/Parsrs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment