Bash 脚本安全计算 Fibonacci 数列及命令替换详解
2024-12-21 10:52:33
Bash 脚本中 Fibonacci 数列计算及命令替换
Fibonacci 数列是一个经典的数学问题,其定义为:除了第一个和第二个数字为 1 之外,其他每个数字都是前两个数字之和。在编写脚本时,难免会遇到使用这个算法的时候。本文重点讨论用 bash 脚本解决 Fibonacci 数列的问题。同时重点讲解如何在 bash 脚本中安全有效地进行命令替换。
Fibonacci 脚本分析与优化
用户需要将原脚本中算术表达式 $(($n0+$n1))
替换为命令 expr $n0 + $n1
,实现同样的功能,同时保留向变量赋值的行为。
原脚本存在一些可以改进的地方。用户提供的代码使用 ‘seq 1 $1‘
在引号中导致变量 $1
无法正确展开,进而可能导致 seq 命令的无效使用,导致计算出错或者运行异常,应该修改为双引号,例如:"seq 1 $1"
,并确认seq在shell环境中被安装和正确识别。
原脚本的流程已经实现了计算 Fibonacci 数列。seq
命令生成一个数字序列,然后通过 for
循环对每个数字进行计算。其中,变量 n0
和 n1
分别保存前两个数字,变量 n
保存当前数字。如果发现前两个变量没有赋值过,且当前的 c
不为 1、2,则进入不到对应的计算逻辑中,导致计算的最终结果错误。原脚本可以通过对 $c
等于 3 进行一次特判进行修复,以保证前两个计算被执行。for
循环的边界和条件的判断决定了脚本的输出内容,使用 $1
对脚本传入计算的数量,用户可以使用和修改这个脚本进行测试和实验。
建议对 $1
进行合法性校验。
#!/bin/bash
if (test $# -ne 1)
then
echo "Use: $0 number"
exit 1
fi
# 添加对 $1 是否为正整数的判断
if ! [[ "$1" =~ ^[0-9]+$ ]] || [ "$1" -eq 0 ]; then
echo "Error: Argument must be a positive integer."
exit 1
fi
for c in `seq 1 $1` #修改为正确的用法
do
if (($c == 1))
then
n0=1;
echo "fibonacci(1)=$n0";
elif (($c == 2));
then
n1=1;
echo "fibonacci(2)=$n1";
elif (($c == 3));
then
n=$(($n0+$n1)); #修复c为3的情况
echo "fibonacci($c)=$n";
n0=$n1;
n1=$n;
else
n=$(($n0+$n1));
echo "fibonacci($c)=$n";
n0=$n1;
n1=$n;
fi
done
针对 $c
等于 1 和 2 的情况可以使用数组提前设定。例如 arr=(0,1,1)
Bash 脚本中的命令替换
Bash 脚本中的命令替换允许捕获一个命令的输出,并将其作为变量的值或者用在另一个命令中。两种常用的命令替换方式:
- 反引号 (`)
- $()
使用 expr
命令和命令替换计算 Fibonacci
使用 expr
命令进行算术运算并替换变量。要捕获 expr $n0 + $n1
的输出,可以这样操作:
方案一:反引号 + expr
直接把 expr $n0 + $n1
放到反引号里,将执行结果赋给 n
。
操作步骤:
- 将
$n0
和$n1
作为expr
命令的参数,用于计算它们的和。 - 用反引号
将
expr n0 + n1` 括起来,执行命令并将标准输出结果替换到当前位置。 - 用
n=
开头,将后面的值赋给n
变量。
#!/bin/bash
if (test $# -ne 1)
then
echo "Use: $0 number"
exit 1
fi
# 添加对 $1 是否为正整数的判断
if ! [[ "$1" =~ ^[0-9]+$ ]] || [ "$1" -eq 0 ]; then
echo "Error: Argument must be a positive integer."
exit 1
fi
for c in `seq 1 $1` #修改为正确的用法
do
if (($c == 1))
then
n0=1;
echo "fibonacci(1)=$n0";
elif (($c == 2));
then
n1=1;
echo "fibonacci(2)=$n1";
elif (($c == 3));
then
n=`expr $n0 + $n1`; #通过反引号将expr的输出结果给n
echo "fibonacci($c)=$n";
n0=$n1;
n1=$n;
else
n=`expr $n0 + $n1`;
echo "fibonacci($c)=$n";
n0=$n1;
n1=$n;
fi
done
方案二:$()
+ expr
把 expr $n0 + $n1
放到 $()
中。这种方式与反引号在功能上类似。
操作步骤:
- 和方案一大体相同。
- 把需要执行的命令换成
$()
。
#!/bin/bash
if (test $# -ne 1)
then
echo "Use: $0 number"
exit 1
fi
# 添加对 $1 是否为正整数的判断
if ! [[ "$1" =~ ^[0-9]+$ ]] || [ "$1" -eq 0 ]; then
echo "Error: Argument must be a positive integer."
exit 1
fi
for c in `seq 1 $1` #修改为正确的用法
do
if (($c == 1))
then
n0=1;
echo "fibonacci(1)=$n0";
elif (($c == 2));
then
n1=1;
echo "fibonacci(2)=$n1";
elif (($c == 3));
then
n=$(expr $n0 + $n1); #通过$()将expr的输出结果给n
echo "fibonacci($c)=$n";
n0=$n1;
n1=$n;
else
n=$(expr $n0 + $n1);
echo "fibonacci($c)=$n";
n0=$n1;
n1=$n;
fi
done
注意: expr
命令在处理变量时,如果变量值为空或未定义,可能会导致错误。为防止用户篡改或者导致脚本注入,例如当 $n0
或 $n1
包含特殊字符或命令时,可能发生执行了预期外命令的情况。为防止篡改,可以在之前对每个计算执行一次有效性校验。确保其为纯数字,可以在每次计算之前使用 if ! [[ "$n0" =~ ^[0-9]+$ ]]
校验两个输入是否正确。如果数字可能产生越界,在一些版本较低的 expr
中可能无法使用计算结果。可以自行开发新的逻辑或使用例如 bc 等其他运算程序完成计算。expr
输出的结尾会多一个空格,根据输出情况决定是否保留或者删去该空格。
通过 $()
的方式,可以将一些逻辑复杂的命令进行封装,并在shell环境内执行,这为我们的工作提供了非常多的便利。
安全的最佳实践:
进行命令替换时,特别是当命令的参数来源于外部输入时,务必小心安全问题。错误的输入可能导致意外的行为,甚至安全漏洞。
-
输入验证: 对所有外部输入的参数进行合法性验证。在这个例子中,确保
$n0
和$n1
是数字。if ! [[ "$n0" =~ ^[0-9]+$ ]] || ! [[ "$n1" =~ ^[0-9]+$ ]]; then echo "Error: Invalid input. n0 and n1 must be integers." exit 1 fi
-
使用双引号: 当展开变量时,始终用双引号将变量引用括起来。这能防止单词分割和通配符扩展引起问题。在命令替换中使用时也是一样的:
n=$(expr "$n0" + "$n1")
-
尽可能不使用外部输入计算变量: 尝试使用bash内置的运算符直接在bash中计算。例如用户的问题可以写作
$((n0 + n1))
。因为当用户可以传递任意参数给脚本时,这些变量被使用的危险性直线上升,并且通过一定的手段和条件能轻易的造成命令注入问题,从而让整个设备收到严重的入侵,务必避免在任何计算中使用用户的输入计算。
使用重定向保存到文件(方案三)
用户提到了将输出保存到文件,但此例中不必如此。若有此需求,可以通过以下方法实现:
操作步骤:
- 使用
expr $n0 + $n1 > output.txt
,命令结果输出到文件而不是直接替换。 - 使用例如
$((cat output.txt))
将文件结果重新读取回bash,再次完成替换,为n
变量赋值。 rm output.txt
可以删去不需要的文件,避免服务器中内容被不断累积。
#!/bin/bash
if (test $# -ne 1)
then
echo "Use: $0 number"
exit 1
fi
if ! [[ "$1" =~ ^[0-9]+$ ]] || [ "$1" -eq 0 ]; then
echo "Error: Argument must be a positive integer."
exit 1
fi
for c in `seq 1 $1`
do
if (($c == 1))
then
n0=1;
echo "fibonacci(1)=$n0";
elif (($c == 2));
then
n1=1;
echo "fibonacci(2)=$n1";
elif (($c == 3));
then
expr $n0 + $n1 > output.txt;
n=$((`cat output.txt`));
rm output.txt
echo "fibonacci($c)=$n";
n0=$n1;
n1=$n;
else
expr $n0 + $n1 > output.txt;
n=$((`cat output.txt`));
rm output.txt
echo "fibonacci($c)=$n";
n0=$n1;
n1=$n;
fi
done
尽管上述方案也可以做到输出并保存计算结果给 n
。但是这个方法并不推荐使用。频繁读写文件会降低性能,尤其是当这个脚本频繁被使用的时候。尽量用 n=$(expr $n0 + $n1)
这种将命令直接输出作为中间的计算的办法进行处理。用户可以通过将原本输出到 output.txt
中的方式修改为直接将 echo $n
命令的结果输出,就可以将每一个结果保存在 output 文件中。这比直接执行 n=$(expr $n0 + $n1)
多出了一次文件 IO,务必谨慎使用。