'*'作为数组元素在拷贝数组过程中引发的灾难性输出!

sh/bash/dash/ksh/zsh等Shell脚本
回复
头像
phi
帖子: 15
注册时间: 2014-01-29 23:06
系统: Ub
送出感谢: 2 次
接收感谢: 0

'*'作为数组元素在拷贝数组过程中引发的灾难性输出!

#1

帖子 phi » 2018-07-10 1:01

[code]
#!/bin/bash
# '*' 作为数组元素在拷贝数组过程中引发的不可预料输出
#

# 举例定义一个仅含一个元素'*'的数组a,含更多元素也一样只要包含特殊符号'*'就会触雷!
a=(\*)
# 问题就出在回显合并或重新赋值数组这里!
b+=(`echo "${a[@]}"`)
# 直接输出数组a的所有元素是正常的
echo "${a[@]}"
# 将数组a回显拷贝给数组b后,输出b的元素就会引发不可预料的灾难输出!
echo "${b[@]}}"
[/code]

如果shell代码处理的数据稍微复杂一点,比如需要向函数传递数组就很容易引发这样的灾难。

请教一下大家有没有什么简洁独到的方法解决这个问题?
头像
astolia
论坛版主
帖子: 4414
注册时间: 2008-09-18 13:11
送出感谢: 1 次
接收感谢: 739 次

Re: '*'作为数组元素在拷贝数组过程中引发的灾难性输出!

#2

帖子 astolia » 2018-07-10 10:24

“灾难性”?你这个用词倒是让我想起了一句话:基础不牢,地动山摇

加对引号算简洁独到了吧

代码: 全选

b+=("`echo "${a[@]}"`")
或者再简洁独到点?

代码: 全选

b+=("${a[@]}")
头像
phi
帖子: 15
注册时间: 2014-01-29 23:06
系统: Ub
送出感谢: 2 次
接收感谢: 0

Re: '*'作为数组元素在拷贝数组过程中引发的灾难性输出!

#3

帖子 phi » 2018-07-11 1:15

astolia 写了:
2018-07-10 10:24
“灾难性”?你这个用词倒是让我想起了一句话:基础不牢,地动山摇

加对引号算简洁独到了吧

代码: 全选

b+=("`echo "${a[@]}"`")
或者再简洁独到点?

代码: 全选

b+=("${a[@]}")

我们打造一件得心应手的“工具”是为了使它更方便我们的日常使用,这就好比“造桥”总是把复杂艰巨的技术挑战留给自己而将方便留给使用它的人。这需要何等胸怀?如果将内部的严刑峻罚直接扩展到外部就如同造一座监狱将所有使用者囚于其中成为工具的奴隶,结果只能是事与愿违。这在软件工程里有个专业词汇叫软件的用户界面是否友好?说的就是这回事。而工具总不会天生就尽善尽美,所以我们需要技术论坛这样的地方畅所欲言相互交流使用经验和心得,这本就是一件轻松愉快的事。打造它的技术团队也可以从中即时发现容易引发错误的地方从而即时修改完善代码增强它的容错机制,最终成就一把众人喜爱的“瑞士军刀”。我想你喜欢苹果手机绝不是因为它的使用手册比较厚实吧?

具体来说一个好的工具应该是能够很好的适应各类用户的不同使用习惯的。比如说我对数组赋值的时侯就习惯于使用echo回显,而将命令执行结果赋给变量的时侯我习惯使用反引号``,不太情愿用$(),这一方面由于括号太多容易引发配对错误另一方面视觉结构层次也不太清晰,还容易和数学运算的$[]混淆。而加了反引号``后再加“”总是让人不太习惯的,至少视觉和心里习惯上我觉得很别扭,如果是$()外加“”可能我们不容易遗漏。这是从一般人的使用习惯上说的,必竟我们不是每写一行代码都用心思考和检查语法和结构可能引发特殊情况下错误的,技巧技能很多时侯是随着经验技法的积累不知不觉溶入到我们的习惯中的。

大可不必因为一时一地的一点点优势自觉高人一等,相互借鉴揣摩心得才是这类技术论坛的价值所在。多年后我们回首哪些都只不过是一个个很小很小的土丘。就像一个孩子终将长大终将会有经验并懂得一切,那时他又会如何回看曾经并不友好的你呢?我们无仇无怨不必老是这么带刺,论坛上的个体谁也不欠谁啥吧?你说到的知识技法我虚心体会,做人做事的态度我不敢苟同。

a=(1 2 3 \*)
b=("`echo "${a[@]}"`")
或者
b=("${a[@]}")
会将以空格分隔的所赋值的数组a的所有元素作为一个完整的字符串赋给数组b,这样的话无论数组a中原来有多少个元素,被赋值的数组b的元素个数其实都只有一个,在后续的处理中将造成更严重的后果!
echo "${#b[@]}"
1
头像
astolia
论坛版主
帖子: 4414
注册时间: 2008-09-18 13:11
送出感谢: 1 次
接收感谢: 739 次

Re: '*'作为数组元素在拷贝数组过程中引发的灾难性输出!

#4

帖子 astolia » 2018-07-11 13:11

你真的试过不用echo的写法?还是你习惯性地在多个成员的数组中用了echo后看见不对就习惯性地直接长篇大论来了?

代码: 全选

$ a=(1 2 3 \*)
$ b=("${a[@]}")
$ echo "${#b[@]}"
4
$ echo "${b[3]}"
*
头像
phi
帖子: 15
注册时间: 2014-01-29 23:06
系统: Ub
送出感谢: 2 次
接收感谢: 0

Re: '*'作为数组元素在拷贝数组过程中引发的灾难性输出!

#5

帖子 phi » 2018-07-12 1:10

astolia 写了:
2018-07-11 13:11
你真的试过不用echo的写法?还是你习惯性地在多个成员的数组中用了echo后看见不对就习惯性地直接长篇大论来了?

代码: 全选

$ a=(1 2 3 \*)
$ b=("${a[@]}")
$ echo "${#b[@]}"
4
$ echo "${b[3]}"
*
请注意我在首贴中提到的为什么要提出这个问题的原因。主要是考虑将一个数组传递给shell函数来处理时极易引发混乱!
在shell里我们不能直接向函数传递整个数组,否则函数仅能接收其第一个元素。要解决这个问题必须将数组变量的值分解为单个值然后将这些值作为函数参数使用,在函数内部将所有接收到的参数重新组合为一个新的数组变量。函数在处理完数据后也不能将整个数组直接返回,只能返回其元素列表。

为了进一步说明“灾难性”后果的表现,我具体举一个比较简单的例子说明。用一个shell函数倒序返回参数数组元素。

代码: 全选


#!/bin/bash

seq=(0 1 2 3 4 5 6 7 8 9 \*)

reverse_order()
{
	local array_in
	local array_out
	# 这是我通常的习惯性做法,在遇到含有'*'的数组元素时会引发不确定的输出灾难!
	array_in=(`echo "[email protected]"`)
	# 以下两句会将传入的所有元素作为一个字符串赋值给新数组
	# array_in=("[email protected]")
	# array_in=("`echo "[email protected]"`")
	
	num_array="${#array_in[@]}"
	# echo "${num_array[@]}"
	k=1
	for i in `seq 0 "$(($num_array-1))"`
	do
		array_out["$i"]="${array_in[$(($num_array-$k))]}"
		((k++))
	done
	echo "${array_out[*]}"
}


# 首先显示一下数组的所有元素,一切正常。
echo "${seq[@]}"
# 用数组seq2接收函数返回的数组元素时也不能加"",否则数组seq2的元素个数将被归一化
seq2=(`reverse_order "${seq[*]}"`)
# echo "${#seq2[@]}"
# 此时如果数组seq中不含'*'则一切正常,含有‘*’时输出会很混乱!
echo "${seq2[@]}"


究其原因其实是在回显的过程中echo将屏敝'*'特殊含义的'\'去掉了。即便我们将‘\’替换为单引号‘’也一样。
如果echo能够添加一个选项强制保留特殊标记‘\’或者单引号就好了。

代码: 全选

echo \*
*
头像
astolia
论坛版主
帖子: 4414
注册时间: 2008-09-18 13:11
送出感谢: 1 次
接收感谢: 739 次

Re: '*'作为数组元素在拷贝数组过程中引发的灾难性输出!

#6

帖子 astolia » 2018-07-12 10:40

看来你没看懂我上面在讽刺你的“习惯”。这些问题早就有了最佳实践,你不去学系统地学习了解,偏要凭习惯自己搞一套。搞出了问题不反思自己的习惯有哪里不对,还反过来指责工具不适应你的习惯。你的“究其原因”,只是暴露了你根本没搞清楚shell执行命令的过程,还是那句话:基础不牢,地动山摇。

让你看看我的做法,再想想你是怎么得出“在shell里我们不能直接向函数传递整个数组”这个结论的

代码: 全选

#!/bin/bash
reverse_order()
{
    local -n array_in="$1"
    local -n array_out="$2"
    local n="${#array_in[@]}"

    for ((i = 0; i < "$n"; i++)) do
        array_out[((n - i))]="${array_in[i]}"
    done
}

seq=(0 1 2 3 4 5 6 7 8 9 \*)
echo "${seq[@]}"
reverse_order seq seq2
echo "${#seq2[@]}"
echo "${seq2[@]}"
头像
phi
帖子: 15
注册时间: 2014-01-29 23:06
系统: Ub
送出感谢: 2 次
接收感谢: 0

Re: '*'作为数组元素在拷贝数组过程中引发的灾难性输出!

#7

帖子 phi » 2018-07-12 23:20

你我的理念不同,各自努力的方向也不同。你会是一个很好的匠人,在当下社会会如鱼得水的。而我注定前路坎坷,注定要去逾越很多如你一样的人,这就是命。我希望这世上每个人都能拥有驾照,而你则坚持没有汽车维修高级职称的人不配申领驾照,这就是我们的不同。我们不一样。
回复

回到 “Shell脚本”