我想知道一种基于文件名统一格式的数据分类方法。文件名如1_dog_yorkshire.sh
和1_cat_persian.sh
可以用简单的正则表达式表示:
[0-9]+_[a-z]+_[a-z]+.sh
我想制作如下所示的树状结构:
1 --- dog ---- yorkshire
| | \
| | -- golden retriever
| |
| -- cat ---- persian
| \
| -- siamese
|
2 --- spider ---- tarantula
首先想到的解决方案是多维关联数组。但是,bash不支持多维数组。哈希表也不是完美的解决方案,因为在Bash中对哈希表进行迭代可能会有问题。在Bash中使用XML / JSON是不可能的,除非它是可移植的并用bash编写。
在理想情况下,任何数据都应该是可迭代的,例如:对于' 2'中的每个条目,对于' 1'中的每只狗。或者用于蜘蛛的蜘蛛列表中的元素' 2'。
如何构建一个足以替代Bash中多维关联数组的结构,可以遍历子树,并且可以存储叶子列表?
答案 0 :(得分:3)
下面是一个黑客,但是......好吧,那已经知道了。 :)
让我们从设置测试数据集开始:
for f in 1_{dog_{yorkshire,"golden retriever"},cat_{persian,siamese}}.sh \
2_spider_tarantula.sh; do
echo "$f" >"$f"
done
然后我们可以为每个文件建立一个环境变量,其中包含一系列内容:
# encode name to be a valid shell variable
translate_name() {
local -a components
local val retval
IFS=_ read -r -a components <<<"$1"
for component in "${components[@]}"; do
val=$(printf '%s' "$component" | base64 - -)
val_eqs=${val//[!=]/}
val_eqs_count=${#val_eqs}
val_no_eqs=${val//=/}
printf -v retval '%s%s_%s__' "$retval" "$val_no_eqs" "$val_eqs_count"
done
printf '%s\n' "${retval%__}"
}
for f in *.sh; do
varname=$(translate_name "${f%.sh}")
mapfile -t "CONTENT_$varname" <"$f"
done
那么,那么 - 让我们说你想要走一个子树。
您可以列出与该子树关联的数组变量:
get_subtree_vars() {
local subst varname
varname=CONTENT_$(IFS=_; translate_name "$*")
printf -v subst '"${!'"$varname"'@}"'
eval 'printf "%s\n" "'"$subst"'"'
}
...并将它们转换回密钥:
# given an encoded variable name, return its original name
# inverse of translate_name
get_name() {
local varname section
local -a sections
for varname; do
retval=
varname=${varname#CONTENT_}
varname=${varname//__/ }
IFS=' ' read -r -a sections <<<"$varname"
for section in "${sections[@]}"; do
val_eqs_count=${section##*_}
val_no_eqs=${section%_*}
val=$val_no_eqs
for (( i=0; i<val_eqs_count; i++ )); do
val+="="
done
retval+=$(base64 -D - - <<<"$val")_
done
printf '%s\n' "${retval%_}"
done
}
...并检索他们的值:
# given an encoded name, retrieve a NUL-delimited list of values stored
# this could be done much more safely with bash 4.3+ using namerefs
get_values() {
local name cmd
local -a values
for name; do
[[ $name = CONTENT_* ]] || name=CONTENT_$name
printf -v cmd 'values=( "${%q[@]}" )' "$name" && eval "$cmd"
printf '%s\0' "${values[@]}"
done
}
# given a name, call a function for each leaf value associated
call_for_each() {
local funcname=$1; shift
while IFS= read -u 3 -r subtree_var; do
while IFS= read -u 4 -r -d '' value; do
"$funcname" "$value"
done 4< <(get_values "$subtree_var")
done 3< <(get_subtree_vars "$@")
}
因此:
printfunc() { printf '%q\n' "$@"; }
call_for_each printfunc 1 cat
...将发出:
1_cat_siamese.sh
1_cat_persian.sh
值得注意的是,这些是数据,而不是元数据 - 注意.sh扩展名,我们在创建时从变量中删除了它!
另外注意:由于使用base64-encoding来清理任何尝试的shell转义,上面代码中的eval
使用应该是安全的逃避尝试(因此通过恶意文件名进行shell注入攻击)可能存在于文件名中; printf %q
用法提供了一个额外的图层。在没有这些保证的情况下,请小心部署上述方法。
所有这一切 - 通过将内容读入内存,以上内容使得事情变得非常复杂。考虑以下自包含代码作为上述示例的替代:
get_subtree_files() {
local prefix
local -a files
prefix=$(IFS=_; printf '%s\n' "$*")
files=( "$prefix"* )
# note that the test only checks the first entry of the array
# ...but that's good enough to detect the no-matches case.
[[ -e $files ]] && printf '%s\0' "${files[@]}"
}
xargs -0 cat < <(get_subtree_files 1 cat)