Shell 变量知多少?

bash shell 编程和其他编程语言差不多,同样包含变量(存放字符串和数值的容器,可以进行修改、比较、传递)。在引用 bash 变量时,可以使用一些非常特殊的运算符。bash 还拥有内建变量,这些变量可以提供有关脚本中其他变量的重要信息。下面介绍 bash 变量和一些特殊的变量引用机制,展示如何将其运用于你自己的脚本。

1、shell 变量基础知识
bash 脚本中的变量名称通常采用全大写,但这并非强制性的,只是一种常见做法而已。变量不用事先声明,直接使用就行了。变量基本上都是字符串类型,不过有些运算符能够将变量内容视为数字。变量的实际用法如下所示。

使用shell变量的普通脚本

MYVAR=“something”
echo $MYVAR

写法类似,但没有引号

MY_2ND=anotherone
echo $MY_2ND

这里因为包含空客,需要使用引号:

MYOTHER=“more stuff to echo”
echo $MYOTHER
bash 变量的语法有两处要点,但可能不那么一目了然。

首先,赋值语法 name=value 看起来相当直观,但 = 两侧不能有任何空白字符。如果允许 = 两侧出现空白字符,那么变量赋值就会变成下面这样:
MYVAR = something
此时 shell 很难区分出到底是要调用命令还是要给变量赋值。对于能够以 = 为参数的命令(如 test)更是如此。所以,还是让事情简单点吧:变量赋值时,shell 不允许在 = 两侧出现空白字符。该规定的另一方面也值得注意,不要在文件名中使用 =。

其次需要注意的是,引用变量时要使用 $ 符号。给变量赋值时不需要在变量名前加 $,但获取变量值时需要。出现在表达式 $(( ))中的变量是个例外。原因很简单,就是为了消除歧义。如下:

MYVAR=something

echo MYVAR is now MYVAR
你能分辨出哪个是字符串 MYVAR,哪个是变量 MYVAR 吗?bash 中的一切都是字符串,所以需要用 $ 来表明变量引

用。

2、记录脚本
详细讨论 shell 脚本或变量前,我们还得说说如何记录脚本。毕竟,你得能看明白自己的脚本,即便是在编写完的几个月后。

用注释记录脚本。# 代表注释的开始。该行上随后的所有字符都会被shell 忽略。

这是一行注释

多用注释

注释是你的好朋友

如果您是java开发工作者,你会发现,这就是我们平时常说的代码注释。

3、将变量名与周围的文本分开
如果你需要输出变量以及其他文本。引用变量要用到 $,但是该怎么区分变量名与紧随其后的其他文本呢?例如,你想要用 shell 变量作为文件名的一部分,如下所示:

for FN in 1 2 3 4 5
do
somescript /tmp/rep$FNport.txt #执行某个脚本,把文件当作执行参数,如cat
done
shell 会怎么理解这段代码?它会认为变量名从 $ 开始,到点号结束。换句话说,它将 $FNport 视为变量名,而非我们想要的 $FN。

那么,我们如何让shell知道我们的变量是FN呢?

使用完整的变量引用语法,不仅要包括 $,还要在变量名周围加上花括号,如下:

somescript /tmp/rep${FN}port.txt
因为 shell 变量名中只能包含字母、数字以及下划线,所以很多时候并不需要使用花括号。任何空白字符或标点符号(下划线除外)都足以提示变量名的结束位置。但只要有疑问,就应该用花括号。

4、导出变量
你在某个脚本中定义了一个变量,但在调用其他脚本时,该脚本并不知道这个变量的存在。为了解决这个问题,我们需要将传给其他脚本的变量导出。如下所示:

export MYVAR

export NAME=value
要想查看所有已导出的变量,敲入命令 env(或者内建命令 export-p)就能列出各个变量及其值。当脚本运行时,这些变量都可供使用,其中很多是 bash 启动脚本已经设置好的,如$PATH。

可以在 export 后面跟上变量赋值,不过这种写法不适用于比较老的 shell 版本。然后导出之后,就可以随意给变量赋值,不用重复导出。因此,有时你会看到下列语句:

导出变量

export FNAME
export SIZE
export MAX

为变量赋值

MAX=2048
SIZE=64
FNAME=/tmp/scratch
注意,导出的变量实际上是按值调用的。在被调用脚本中修改导出变量的值并不会改变调用脚本中该变量的值。

对于导出的变量,我们该如何删除呢?

删除变量

unset myvar
Shell 变量(二)
你希望用户能在调用脚本时指定参数。可以要求用户设置一个 shell变量,但这种做法似乎不够灵活。另外还需要向其他脚本传递数据。这可以通过环境变量实现,但会使两个脚本之间的联系过于紧密。因此,此处我们可以用到脚本参数。

1、在shell脚本中使用参数
使用命令行参数。在命令行上,出现在脚本名之后的任意单词都可以在脚本中作为编号变量(numbered variable)被访问。假设有下列脚本 simplest.sh。

一个简单的shell脚本

echo $1
该脚本会显示在命令行上被调用时所指定的第一个参数。我们来看一种实际用法。

$ cat simplest.sh

一个简单的shell脚本

echo ${1}
$ ./simplest.sh you see what I mean
you
$ ./simplest.sh one more time
one
$
其他参数的可用形式分别为 2 、 {2}、 2{3}、 4 、 {4}、 4{5} 等。单个数位的数字用不着花括号,除非要区分变量名与其后出现的文本。典型的脚本只用到少部分参数,但如果涉及 ${10},那就得使用花括号了,否则 shell 会将 $10 理解为 ${1} 后面紧跟着字符串 0,如下所示。

$ cat tricky.sh
echo $1 $10 ${10}
$ ./tricky.sh I II III IV V VI VII VIII IX X XI
I I0 X #注意观察第二个输出
$
第 10 个参数的值是 X,但如果在脚本中写成 $10,那么你在 echo语句中得到的会是第一个参数 $1,后面紧跟着一个字符串 0。

因为第三个使用了 , 所以三个 {},所以三个 ,所以三个{10}可以正常输出X。

2、遍历传入脚本的参数: $*
如果你想对指定的一系列参数执行某些操作。在编写 shell 脚本时,对单个参数进行处理不是什么问题,只需要用 $1 引用这个参数即可。但如果面对的是一大批文件呢?你可能想这样调用脚本。

./actall *.txt
shell 会进行模式匹配,生成匹配 *.txt 模式(以 .txt 结尾的文件名)的文件名列表。对于脚本而言,我们永远无法预估传入的参数的个数,那么我们就无法通过 数字获取所有参数,那么 {数字}获取所有参数,那么 数字获取所有参数,那么{数字}方式将不再适用。

特殊的 shell 变量 $* 能够引用所有的参数,可以将其用于 for 循环,如下所示:

#!/usr/bin/env bash

实例文件:actall.sh

批量修改文件权限

for FN in $*
do
echo changing $FN
chmod 0750 $FN
done
变量 F N 是我们自己挑选的;使用别的变量名也没有任何问题。 FN 是我们自己挑选的;使用别的变量名也没有任何问题。 FN是我们自己挑选的;使用别的变量名也没有任何问题。*引用的是命令行上出现的所有参数。假如用户输入

./actall abc.txt another.txt allmynotes.txt
调用该脚本时,$1 等于 abc.txt、$2 等于 another.txt、$3 等于allmynotes.txt,而 $ 等于整个参数列表。换句话说,shell 替换for 语句中的 $ 后,脚本就变成了如下这样:

for FN in abc.txt another.txt allmynotes.txt
do
echo changing $FN
chmod 0750 $FN
done
for 循环从列表中获取第一个值,并将其赋给变量 $FN,然后执行do 和 done 之间的语句。列表中的其他值会重复执行该过程。

3、处理包含空格的参数: “”
你编写了一个可以接受文件名作为参数的脚本,看起来一切正常,但有一次脚本出现了问题,结果发现是因为文件名中带有空格。你得仔细将所有可能包含文件名的命令参数全部加上引号。引用变量时,将其放入双引号中。

在 shell 脚本中,曾经简单的写作 ls -l $1 的地方,现在最好给参数加上引号,改写成 ls -l “$1”。否则,如果参数包含空格,那么会被 shell 解析成两个单词,$1 中只会包含部分文件名。如下:

$ cat simpls.sh

一个简单的shell脚本

ls -l ${1}
$
$ ./simple.sh Oh the Waste
ls: Oh: No such file or directory
$
如果调用脚本时没有将文件名放进引号,那么 bash 会看到 3 个参数并将 $1 替换成第 1 个参数(Oh)。ls 命令运行时只有一个参数Oh,结果就是无法找到该文件。

接下来,我们在调用脚本时给文件名加上引号。

$ ./simpls.sh “Oh the Waste”
ls: Oh: No such file or directory
ls: the: No such file or directory
ls: Waste: No such file or directory
$
还是不行。bash 得到了一个包含 3 个单词的文件名,并将 ls 命令中的 $1 替换成了该文件名。到目前一切都还好。但是,我们并没有将脚本中的变量引用放入引号,因此 ls 将文件名中的各个单词视为单独的参数(作为单独的文件名)。结果还是无法找到这些文件,相当执行命令:

ls -l Oh the Waste
因此,我们需要将我们变量引用放进引号,修改脚本内容如下:

$ cat simpls.sh

一个简单的shell脚本,注意此处${1}与第一次脚本里的区别,多了双引号

ls -l “${1}”
$
$ ./simple.sh “Oh the Waste”
$
4、处理包含空格的参数列表: @ 对于第二节,我们通过 @ 对于第二节,我们通过 @对于第二节,我们通过*,可以获取参数列表,那么如果这个时候我们传入的参数列表包含空格会不会有问题呢? 如下所示:

$ ./actall.sh “Oh the Waste”
changing Oh
chmod: 无 法 访 问 “Oh”: 没 有 那 个 文 件 或 目 录
changing the
chmod: 无 法 访 问 “the”: 没 有 那 个 文 件 或 目 录
changing Waste
chmod: 无 法 访 问 “Waste”: 没 有 那 个 文 件 或 目 录
$
按照上节中的建议,你给变量加上引号,但是仍然出现了错误。如下:

#!/usr/bin/env bash
#实例文件:actall.sh
#批量修改文件权限

for FN in ∗ d o e c h o c h a n g i n g " * do echo changing " doechochanging"FN"
chmod 0750 “$FN”
done

如果文件名中带有空格,就会报错,报错的原因与 for 循环中使用的 $* 有关。在这个示例中,我们需要用到另一个不同但相关的 shell 变量 $@。如果该变量出现在引号中,则会得到一个命令行参数列表,其中每个参数都会被单独引用起来。修改后的 shell 脚本如下:

#!/usr/bin/env bash

实例文件:chmod_all.2

在文件名包含空格时选择更好的引号添加方式,批量修改文件权限

for FN in “ @ " d o c h m o d 0750 " @" do chmod 0750 " @"dochmod0750"FN”
done
如果不加引号,$* 和 @ 没什么两样。但当两者出现在引号中时, b a s h 就会区别对待了。 " @ 没什么两样。但当两者出现在引号中时,bash 就会区别对待了。" @没什么两样。但当两者出现在引号中时,bash就会区别对待了。"*" 得到的是整个参数列表,而"$@" 得到的可不是一个字符串,而是与各个参数对应的带有引号的字符串列表。

如果你知道文件名中没有空格,沿用老的 ∗ 语法基本没什么大碍。对于那些更稳健的脚本而言,安全起见,建议使用 " * 语法基本没什么大碍。对于那些更稳健的脚本而言,安全起见,建议使用 " 语法基本没什么大碍。对于那些更稳健的脚本而言,安全起见,建议使用"@"

Shell 变量(三)
1、统计参数数量
你想知道调用脚本时使用了多少个参数。使用 shell 内建变量 $#。如下,展示了一个严格要求3个参数的脚本:

#!/usr/bin/env bash

实例文件:check_arg_count

检查正确的参数数量:

使用下列语法或者:if [ $# -lt 3 ]

if (( $# < 3 ))
then
printf “%b” “Error. Not enough arguments.\n” >&2
printf “%b” “usage: myscript file1 op file2\n” >&2
exit 1
elif (( $# > 3 ))
then
printf “%b” “Error. Too many arguments.\n” >&2
printf “%b” “usage: myscript file1 op file2\n” >&2
exit 2
else
printf “%b” “Argument count correct. Proceeding…\n”
fi
以下分别是参数过多和参数数量正好时的运行情况。

$ ./myscript myfile is copied into yourfile
Error. Too many arguments.
usage: myscript file1 op file2

$ ./myscript myfile copy yourfile
Argument count correct. Proceeding…
我们用 if 测试所提供的参数数量(保存在 $# 中)是否大于 3。如果答案是肯定的,则输出一条错误信息,提醒用户正确的脚本用法,然后退出。
https://www.xiaohongshu.com/discovery/item/630e0514000000001200bf73
https://www.xiaohongshu.com/discovery/item/630e033b00000000120155f0
https://www.xiaohongshu.com/discovery/item/630b8776000000001b019a78
https://www.xiaohongshu.com/discovery/item/630b596e000000001800e0da

标准提示错误信息会被重定向到标准错误(>&2)。这种做法符合标准错误的本意:作为所有错误信息的通道。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值