注:本文为 “Bash 脚本编写” 相关文章合辑。
BASH 脚本编写教程
as good as well于 2017-08-04 22:04:28 发布
这里有个老 American 写的 BASH 脚本编写教程,非常不错,至少没接触过 BASH 的也能看懂!
建立一个脚本
Linux 中有好多中不同的 shell,但是通常我们使用 bash (bourne again shell) 进行 shell 编程,因为 bash 是免费的并且很容易使用。所以在本文中笔者所提供的脚本都是使用 bash(但是在大多数情况下,这些脚本同样可以在 bash 的大姐,bourne shell 中运行)。
如同其他语言一样,通过我们使用任意一种文字编辑器,比如 nedit、kedit、emacs、vi 等来编写我们的 shell 程序。
程序必须以下面的行开始(必须放在文件的第一行):
#!/bin/sh
符号 #!
用来告诉系统它后面的参数是用来执行该文件的程序。在这个例子中我们使用 /bin/sh
来执行程序。
当编辑好脚本时,如果要执行该脚本,还必须使其可执行。
要使脚本可执行:
chmod +x filename
然后,您可以通过输入:./filename
来执行您的脚本。
注释
在进行 shell 编程时,以 #
开头的句子表示注释,直到这一行的结束。我们真诚地建议您在程序中使用注释。如果您使用了注释,那么即使相当长的时间内没有使用该脚本,您也能在很短的时间内明白该脚本的作用及工作原理。
变量
在其他编程语言中您必须使用变量。在 shell 编程中,所有的变量都由字符串组成,并且您不需要对变量进行声明。要赋值给一个变量,您可以这样写:
变量名=值
取出变量值可以加一个美元符号($
)在变量前面:
#!/bin/sh
#对变量赋值:
a="hello world"
# 现在打印变量a的内容:
echo "A is:"
echo $a
在您的编辑器中输入以上内容,然后将其保存为一个文件first
。之后执行chmod +x first
使其可执行,最后输入./first
执行该脚本。
这个脚本将会输出:
A is:
hello world
有时候变量名很容易与其他文字混淆,比如:
num=2
echo "this is the $numnd"
这并不会打印出"this is the 2nd",而仅仅打印"this is the ",因为shell会去搜索变量numnd
的值,但是这个变量是没有值的。可以使用花括号来告诉shell我们要打印的是num
变量:
num=2
echo "this is the ${num}nd"
这将打印: this is the 2nd
有许多变量是系统自动设定的,这将在后面使用这些变量时进行讨论。
如果您需要处理数学表达式,那么您需要使用诸如expr
等程序(见下面)。
除了一般的仅在程序内有效的shell变量以外,还有环境变量。由export
关键字处理过的变量叫做环境变量。我们不对环境变量进行讨论,因为通常情况下仅仅在登录脚本中使用环境变量。
Shell 命令和流程控制
在 shell 脚本中可以使用三类命令:
1) Unix 命令:
虽然在 shell 脚本中可以使用任意的 unix 命令,但是还是由一些相对更常用的命令。这些命令通常是用来进行文件和文字操作的。
常用命令语法及功能
echo "some text"
: 将文字内容打印在屏幕上ls
: 文件列表wc –l file
wc -w file
wc -c file
: 计算文件行数计算文件中的单词数计算文件中的字符数cp sourcefile destfile
: 文件拷贝mv oldname newname
: 重命名文件或移动文件rm file
: 删除文件grep 'pattern' file
: 在文件内搜索字符串比如:grep 'searchstring' file.txt
cut -b colnum file
: 指定欲显示的文件内容范围,并将它们输出到标准输出设备比如:输出每行第5个到第9个字符cut -b5-9 file.txt
千万不要和cat
命令混淆,这是两个完全不同的命令cat file.txt
: 输出文件内容到标准输出设备(屏幕)上file somefile
: 得到文件类型read var
: 提示用户输入,并将输入赋值给变量sort file.txt
: 对file.txt
文件中的行进行排序uniq
: 删除文本文件中出现的行列比如:sort file.txt \| uniq
expr
: 进行数学运算Example: add 2 and 3expr 2 "+" 3
find
: 搜索文件比如:根据文件名搜索find . -name filename -print
tee
: 将数据输出到标准输出设备(屏幕) 和文件比如:somecommand \| tee outfile
basename file
: 返回不包含路径的文件名比如:basename /bin/tux
将返回tux
dirname file
: 返回文件所在路径比如:dirname /bin/tux
将返回/bin
head file
: 打印文本文件开头几行tail file
: 打印文本文件末尾几行sed
: Sed是一个基本的查找替换程序。可以从标准输入(比如命令管道)读入文本,并将结果输出到标准输出(屏幕)。该命令采用正则表达式(见参考)进行搜索。不要和shell中的通配符相混淆。比如:将linuxfocus
替换为LinuxFocus
:cat text.file \| sed 's/linuxfocus/LinuxFocus/' > newtext.file
awk
: awk 用来从文本文件中提取字段。缺省地,字段分割符是空格,可以使用-F
指定其他分割符。cat file.txt \| awk -F, '{print $1 "," $3 }'
这里我们使用,作为字段分割符,同时打印第一个和第三个字段。如果该文件内容如下:Adam Bor, 34, IndiaKerry Miller, 22, USA
命令输出结果为:Adam Bor, IndiaKerry Miller, USA
2) 概念: 管道, 重定向和 backtick
这些不是系统命令,但是他们真的很重要。
- 管道 (
|
) 将一个命令的输出作为另外一个命令的输入。 - 重定向:将命令的结果输出到文件,而不是标准输出(屏幕)。
>
写入文件并覆盖旧文件>>
加到文件的尾部,保留旧文件内容。
- 反短斜线 (
\
) 使用反短斜线可以将一个命令的输出作为另外一个命令的一个命令行参数。
3) 流程控制
“if” 表达式 如果条件为真则执行then后面的部分:
if ....; then
....
elif ....; then
....
else
....
fi
大多数情况下,可以使用测试命令来对条件进行测试。比如可以比较字符串、判断文件是否存在及是否可读等等…
通常用" [ ] "来表示条件测试。注意这里的空格很重要。要确保方括号的空格。
[ -f "somefile" ] :判断是否是一个文件
[ -x "/bin/ls" ] :判断/bin/ls是否存在并有可执行权限
[ -n "$var" ] :判断$var变量是否有值
[ "$a" = "$b" ] :判断$a和$b是否相等
执行man test
可以查看所有测试表达式可以比较和判断的类型。
直接执行以下脚本:
#!/bin/sh
if [ "$SHELL" = "/bin/bash" ]; then
echo "your login shell is the bash (bourne again shell)"
else
echo "your login shell is not bash but $SHELL"
fi
变量$SHELL包含了登录shell的名称,我们和/bin/bash
进行了比较。
快捷操作符
熟悉C语言的朋友可能会很喜欢下面的表达式:
[ -f "/etc/shadow" ] && echo "This computer uses shadow passwors"
这里 &&
就是一个快捷操作符,如果左边的表达式为真则执行右边的语句。您也可以认为是逻辑运算中的与操作。上例中表示如果/etc/shadow
文件存在则打印” This computer uses shadow passwors”。同样或操作(||
)在shell编程中也是可用的。这里有个例子:
#!/bin/sh
mailfolder=/var/spool/mail/james
[ -r "$mailfolder" ]' '{ echo "Can not read $mailfolder" ; exit 1; }
echo "$mailfolder has mail from:"
grep "^From " $mailfolder
该脚本首先判断mailfolder
是否可读。如果可读则打印该文件中的"From" 一行。如果不可读则或操作生效,打印错误信息后脚本退出。这里有个问题,那就是我们必须有两个命令:
- 打印错误信息
- 退出程序
我们使用花括号以匿名函数的形式将两个命令放到一起作为一个命令使用。一般函数将在下文提及。
不用与和或操作符,我们也可以用if表达式作任何事情,但是使用与或操作符会更便利很多。
case 表达式
可以用来匹配一个给定的字符串,而不是数字。
case ... in
...
) do something ;;
...
esac
让我们看一个例子。 file命令可以辨别出一个给定文件的文件类型,比如:
file lf.gz
这将返回:
lf.gz: gzip compressed data, deflated, original filename,
last modified: Mon Aug 27 23:09:18 2001, os: Unix
我们利用这一点写了一个叫做smartzip的脚本,该脚本可以自动解压bzip2, gzip 和zip 类型的压缩文件:
#!/bin/sh
ftype=`file "$1"`
case "$ftype" in
"$1: Zip archive"*)
unzip "$1" ;;
"$1: gzip compressed"*)
gunzip "$1" ;;
"$1: bzip2 compressed"*)
bunzip2 "$1" ;;
*)
error "File $1 can not be uncompressed with smartzip";;
esac
您可能注意到我们在这里使用了一个特殊的变量$1
。该变量包含了传递给该程序的第一个参数值。也就是说,当我们运行:
smartzip articles.zip
$1
就是字符串 articles.zip
select 表达式
是一种bash的扩展应用,尤其擅长于交互式使用。用户可以从一组不同的值中进行选择。
select var in ... ; do
break
done
.... now $var can be used ....
下面是一个例子:
#!/bin/sh
echo "What is your favourite OS?"
select var in "Linux" "Gnu Hurd" "Free BSD" "Other"; do
break
done
echo "You have selected $var"
下面是该脚本运行的结果:
What is your favourite OS?
1) Linux
2) Gnu Hurd
3) Free BSD
4) Other
#? 1
You have selected Linux
循环表达式
while-loop
while ...; do
....
done
while-loop 将运行直到表达式测试为真。关键字"break" 用来跳出循环。而关键字”continue”用来不执行余下的部分而直接跳到下一个循环。
for-loop
for var in ....; do
....
done
在下面的例子中,将分别打印ABC到屏幕上:
#!/bin/sh
for var in A B C ; do
echo "var is $var"
done
下面是一个更为有用的脚本showrpm,其功能是打印一些RPM包的统计信息:
#!/bin/sh
# list a content summary of a number of RPM packages
# USAGE: showrpm rpmfile1 rpmfile2 ...
# EXAMPLE: showrpm /cdrom/RedHat/RPMS/*.rpm
for rpmpackage in $*; do
if [ -r "$rpmpackage" ];then
echo "=============== $rpmpackage =============="
rpm -qi -p $rpmpackage
else
echo "ERROR: cannot read file $rpmpackage"
fi
done
这里出现了第二个特殊的变量 $*
,该变量包含了所有输入的命令行参数值。如果您运行 showrpm openssh.rpm w3m.rpm webgrep.rpm
,此时 $*
包含了 3 个字符串,即 openssh.rpm
, w3m.rpm
and webgrep.rpm
.
引号
在向程序传递任何参数之前,程序会扩展通配符和变量。这里所谓扩展的意思是程序会把通配符(比如 *
)替换成合适的文件名,它变量替换成变量值。为了防止程序作这种替换,您可以使用引号:让我们来看一个例子,假设在当前目录下有一些文件,两个 jpg 文件, mail.jpg 和 tux.jpg。
#!/bin/sh
echo *.jpg
这将打印出 “mail.jpg tux.jpg” 的结果。
引号 (单引号和双引号)将防止这种通配符扩展:
#!/bin/sh
echo "*.jpg"
echo '\*.jpg'
这将打印 “*.jpg” 两次。
单引号更严格一些。它可以防止任何变量扩展。双引号可以防止通配符扩展但允许变量扩展。
#!/bin/sh
echo $SHELL
echo "$SHELL"
echo '$SHELL'
运行结果为:
/bin/bash
/bin/bash
$SHELL
最后,还有一种防止这种扩展的方法,那就是使用转义字符——反斜杆:
echo \*.jpg
echo $SHELL
这将输出:
*.jpg
$SHELL
Here documents
当要将几行文字传递给一个命令时,here documents 一种不错的方法。对每个脚本写一段帮助性的文字是很有用的,此时如果我们使用那个 here documents 就不必用 echo 函数一行行输出。 一个 “Here document” 以 <<
开始,后面接上一个字符串,这个字符串还必须出现在 here document 的末尾。下面是一个例子,在该例子中,我们对多个文件进行重命名,并且使用 here documents 打印帮助:
#!/bin/sh
# we have less than 3 arguments. Print the help text:
if [ $# -lt 3 ] ; then
cat <
ren -- renames a number of files using sed regular expressions
USAGE: ren 'regexp' 'replacement' files...
EXAMPLE: rename all *.HTM files in *.html:
ren 'HTM$' 'html' *.HTM
HELP
exit 0
fi
OLD="$1"
NEW="$2"
# The shift command removes one argument from the list of
# command line arguments.
shift
shift
# $* contains now all the files:
for file in $*; do
if [ -f "$file" ] ; then
newfile=`echo "$file" | sed "s/${OLD}/${NEW}/g"`
if [ -f "$newfile" ]; then
echo "ERROR: $newfile exists already"
else
echo "renaming $file to $newfile ..."
mv "$file" "$newfile"
fi
fi
done
这是一个复杂一些的例子。让我们详细讨论一下。第一个 if 表达式判断输入命令行参数是否小于 3 个 (特殊变量 $#
表示包含参数的个数)。如果输入参数小于 3 个,则将帮助文字传递给 cat
命令,然后由 cat
命令将其打印在屏幕上。打印帮助文字后程序退出。如果输入参数等于或大于 3 个,我们就将第一个参数赋值给变量 OLD
,第二个参数赋值给变量 NEW
。下一步,我们使用 shift
命令将第一个和第二个参数从参数列表中删除,这样原来的第三个参数就成为参数列表 $*
的第一个参数。然后我们开始循环,命令行参数列表被一个接一个地被赋值给变量 $file
。接着我们判断该文件是否存在,如果存在则通过 sed
命令搜索和替换来产生新的文件名。然后将反短斜线内命令结果赋值给 newfile
。这样我们就达到了我们的目的:得到了旧文件名和新文件名。然后使用 mv
命令进行重命名。
函数
如果您写了一些稍微复杂一些的程序,您就会发现在程序中可能在几个地方使用了相同的代码,并且您也会发现,如果我们使用了函数,会方便很多。一个函数是这个样子的:
functionname()
{
# inside the body $1 is the first argument given to the function
# $2 the second ...
body
}
您需要在每个程序的开始对函数进行声明。
下面是一个叫做xtitlebar的脚本,使用这个脚本您可以改变终端窗口的名称。这里使用了一个叫做help的函数。正如您可以看到的那样,这个定义的函数被使用了两次。
#!/bin/sh
# vim: set sw=4 ts=4 et:
help()
{
cat <
xtitlebar -- change the name of an xterm, gnome-terminal or kde konsole
USAGE: xtitlebar [-h] "string_for_titelbar"
OPTIONS: -h help text
EXAMPLE: xtitlebar "cvs"
HELP
exit 0
}
# in case of error or if -h is given we call the function help:
[ -z "$1" ] && help
[ "$1" = "-h" ] && help
# send the escape sequence to change the xterm titelbar:
echo -e "33]0;$107"
在脚本中提供帮助是一种很好的编程习惯,这样方便其他用户(和您)使用和理解脚本。
命令行参数
我们已经见过$*
和 $1
, $2
… $9
等特殊变量,这些特殊变量包含了用户从命令行输入的参数。迄今为止,我们仅仅了解了一些简单的命令行语法(比如一些强制性的参数和查看帮助的-h
选项)。但是在编写更复杂的程序时,您可能会发现您需要更多的自定义的选项。通常的惯例是在所有可选的参数之前加一个减号,后面再加上参数值 (比如文件名)。
有好多方法可以实现对输入参数的分析,但是下面的使用case表达式的例子无遗是一个不错的方法。
#!/bin/sh
help()
{
cat <
This is a generic command line parser demo.
USAGE EXAMPLE: cmdparser -l hello -f -- -somefile1 somefile2
HELP
exit 0
}
while [ -n "$1" ]; do
case $1 in
-h) help;shift 1;; # function help is called
-f) opt_f=1;shift 1;; # variable opt_f is set
-l) opt_l=$2;shift 2;; # -l takes an argument -> shift by 2
--) shift;break;; # end of options
-*) echo "error: no such option $1. -h for help";exit 1;;
*) break;;
esac
done
echo "opt_f is $opt_f"
echo "opt_l is $opt_l"
echo "first arg is $1"
echo "2nd arg is $2"
您可以这样运行该脚本:
cmdparser -l hello -f -- -somefile1 somefile2
返回的结果是:
opt_f is 1
opt_l is hello
first arg is -somefile1
2nd arg is somefile2
这个脚本是如何工作的呢?脚本首先在所有输入命令行参数中进行循环,将输入参数与case表达式进行比较,如果匹配则设置一个变量并且移除该参数。根据unix系统的惯例,首先输入的应该是包含减号的参数。
实例
一般编程步骤
现在我们来讨论编写一个脚本的一般步骤。任何优秀的脚本都应该具有帮助和输入参数。并且写一个伪脚本(framework.sh),该脚本包含了大多数脚本都需要的框架结构,是一个非常不错的主意。这时候,在写一个新的脚本时我们只需要执行一下copy命令:
cp framework.sh myscript
然后再插入自己的函数。
让我们再看两个例子:
二进制到十进制的转换
脚本 b2d 将二进制数 (比如 1101) 转换为相应的十进制数。这也是一个用expr
命令进行数学运算的例子:
#!/bin/sh
# vim: set sw=4 ts=4 et:
help()
{
cat <
b2h -- convert binary to decimal
USAGE: b2h [-h] binarynum
OPTIONS: -h help text
EXAMPLE: b2h 111010
will return 58
HELP
exit 0
}
error()
{
# print an error and exit
echo "$1"
exit 1
}
lastchar()
{
# return the last character of a string in $rval
if [ -z "$1" ]; then
# empty string
rval=""
return
fi
# wc puts some space behind the output this is why we need sed:
numofchar=`echo -n "$1" | wc -c | sed 's/ //g' `
# now cut out the last char
rval=`echo -n "$1" | cut -b $numofchar`
}
chop()
{
# remove the last character in string and return it in $rval
if [ -z "$1" ]; then
# empty string
rval=""
return
fi
# wc puts some space behind the output this is why we need sed:
numofchar=`echo -n "$1" | wc -c | sed 's/ //g' `
if [ "$numofchar" = "1" ]; then
# only one char in string
rval=""
return
fi
numofcharminus1=`expr $numofchar - 1`
# now cut all but the last char:
rval=`echo -n "$1" | cut -b 0-${numofcharminus1}`
}
while [ -n "$1" ]; do
case $1 in
-h) help;shift 1;; # function help is called
--) shift;break;; # end of options
-*) error "error: no such option $1. -h for help";;
*) break;;
esac
done
# The main program
sum=0
weight=1
# one arg must be given:
[ -z "$1" ] && help
binnum="$1"
binnumorig="$1"
while [ -n "$binnum" ]; do
lastchar "$binnum"
if [ "$rval" = "1" ]; then
sum=`expr "$weight" "+" "$sum"`
fi
# remove the last position in $binnum
chop "$binnum"
binnum="$rval"
weight=`expr "$weight" "*" 2`
done
echo "binary $binnumorig is decimal $sum"
该脚本使用的算法是利用十进制和二进制数权值 (1,2,4,8,16,…),比如二进制"10"可以这样转换成十进制:
[ 0 * 1 + 1 * 2 = 2 ]
为了得到单个的二进制数我们是用了 lastchar 函数。该函数使用 wc –c 计算字符个数,然后使用 cut 命令取出末尾一个字符。Chop 函数的功能则是移除最后一个字符。
文件循环程序
或许您是想将所有发出的邮件保存到一个文件中的人们中的一员,但是在过了几个月以后,这个文件可能会变得很大以至于使对该文件的访问速度变慢。下面的脚本 rotatefile 可以解决这个问题。这个脚本可以重命名邮件保存文件(假设为 outmail)为 outmail.1,而对于 outmail.1 就变成了 outmail.2 等等等等…
#!/bin/sh
# vim: set sw=4 ts=4 et:
ver="0.1"
help()
{
cat <
rotatefile -- rotate the file name
USAGE: rotatefile [-h] filename
OPTIONS: -h help text
EXAMPLE: rotatefile out
This will e.g rename out.2 to out.3, out.1 to out.2, out to out.1
and create an empty out-file
The max number is 10
version $ver
HELP
exit 0
}
error()
{
echo "$1"
exit 1
}
while [ -n "$1" ]; do
case $1 in
-h) help;shift 1;;
--) break;;
-*) echo "error: no such option $1. -h for help";exit 1;;
*) break;;
esac
done
# input check:
if [ -z "$1" ]; then
error "ERROR: you must specify a file, use -h for help"
fi
filen="$1"
# rename any .1 , .2 etc file:
for n in 9 8 7 6 5 4 3 2 1; do
if [ -f "$filen.$n" ]; then
p=`expr $n + 1`
echo "mv $filen.$n $filen.$p"
mv $filen.$n $filen.$p
fi
done
# rename the original file:
if [ -f "$filen" ]; then
echo "mv $filen $filen.1"
mv $filen $filen.1
fi
echo touch $filen
touch $filen
这个脚本是如何工作的呢?在检测用户提供了一个文件名以后,我们进行一个 9 到 1 的循环。文件 9 被命名为 10,文件 8 重命名为 9 等等。循环完成之后,我们将原始文件命名为文件 1 同时建立一个与原始文件同名的空文件。
调试
最简单的调试命令当然是使用 echo 命令。您可以使用 echo 在任何怀疑出错的地方打印任何变量值。这也是绝大多数的 shell 程序员要花费 80% 的时间来调试程序的原因。Shell 程序的好处在于不需要重新编译,插入一个 echo 命令也不需要多少时间。
shell 也有一个真实的调试模式。如果在脚本 “strangescript” 中有错误,您可以这样来进行调试:
sh -x strangescript
这将执行该脚本并显示所有变量的值。
shell还有一个不需要执行脚本只是检查语法的模式。可以这样使用:
sh -n your_script
这将返回所有语法错误。
Bash 脚本
阿汤哥的程序之路于 2021-05-26 10:21:54 发布
什么是 Bash
简介
Bash(GNU Bourne-Again Shell)是一个为 GNU 计划编写的 Unix shell,它是许多 Linux 平台默认使用的 shell。
shell 是一个命令解释器,是介于操作系统内核与用户之间的一个绝缘层。准确地说,它也是能力很强的计算机语言,被称为解释性语言或脚本语言。它可以通过将系统调用、公共程序、工具和编译过的二进制程序”粘合“在一起来建立应用,这是大多数脚本语言的共同特征,所以有时候脚本语言又叫做“胶水语言”。
事实上,所有的 UNIX 命令和工具再加上公共程序,对于 shell 脚本来说,都是可调用的。Shell 脚本对于管理系统任务和其它的重复工作的例程来说,表现的非常好,根本不需要那些华而不实的成熟紧凑的编译型程序语言。
运行 Bash 脚本的方式:
#!/bin/bash
#这一行是表示使用 /bin/bash 作为脚本的解释器
#这行要放在脚本的行首并且不要省略。
# 使用shell来执行
sh hello.sh
# 使用bash来执行
bash hello.sh
# 使用.来执行
. ./hello.sh
# 使用source来执行
source hello.sh
# 还可以赋予脚本所有者执行权限,允许该用户执行该脚本
chmod u+rx hello.sh
./hello.sh
bash特殊字符(上)
注释(#)
行首以 # 开头(除#!之外)的是注释。#! 是用于指定当前脚本的解释器,我们这里为 bash,且应该指明完整路径,所以为 /bin/bash
分号(;)
使用分号 ; 可以在同一行上写两个或两个以上的命令。
点号(.)
等价于 source 命令
bash 中的 source 命令用于在当前 bash 环境下读取并执行 FileName.sh 中的命令。
source test.sh
. test.sh
引号
双引号(")
“STRING” 将会阻止(解释)STRING 中大部分特殊的字符。后面的实验会详细说明。
单引号(’)
‘STRING’ 将会阻止 STRING 中所有特殊字符的解释,这是一种比使用"更强烈的形式。后面的实验会详细说明。
反引号(`)
命令替换
反引号中的命令会优先执行,如:
cp `mkdir back` test.sh back
ls
先创建了 back 目录,然后复制 test.sh 到 back 目录。
冒号(:)
空命令
等价于“NOP”(no op,一个什么也不干的命令)。也可以被认为与 shell 的内建命令 true 作用相同。“:”命令是一个 bash 的内建命令,它的退出码(exit status)是(0)。
#!/bin/bash
while :
do
echo "endless loop"
done
等价于
#!/bin/bash
while true
do
echo "endless loop"
done
可以在 if/then 中作占位符:
#!/bin/bash
condition=5
if [ $condition -gt 0 ]
#gt表示greater than,也就是大于,同样有-lt(小于),-eq(等于)
then : # 什么都不做,退出分支
else
echo "$condition"
fi
变量扩展/子串替换
在与 > 重定向操作符结合使用时,将会把一个文件清空,但是并不会修改这个文件的权限。如果之前这个文件并不存在,那么就创建这个文件。
: > test.sh # 文件“test.sh”现在被清空了
# 与 cat /dev/null > test.sh 的作用相同
# 然而,这并不会产生一个新的进程, 因为“:”是一个内建命令
在与 >> 重定向操作符结合使用时,将不会对预先存在的目标文件 : >> target_file 产生任何影响。如果这个文件之前并不存在,那么就创建它。
问号(?)
测试操作符
在一个双括号结构中,? 就是 C 语言的三元操作符,如:
vim test.sh
输入如下代码,并保存:
#!/bin/bash
a=10
(( t=a<50?8:9 ))
echo $t
运行测试
bash test.sh
8
美元符号($)
引用变量
bash特殊字符(下)
小括号(( ))
在括号中的变量,由于是在子 shell 中,所以对于脚本剩下的部分是不可用的。父进程,也就是脚本本身,将不能够读取在子进程中创建的变量,也就是在子 shell 中创建的变量。如:
vim test20.sh
输入代码:
#!/bin/bash
( a=321; )
运行代码:
bash test20.sh
在圆括号中 a 变量,更像是一个局部变量。
初始化数组
创建数组
vim test21.sh
输入代码:
#!/bin/bash
arr=(1 4 5 7 9 21)
echo ${arr[3]} # get a value of arr
运行代码:
bash test21.sh
7
大括号({ })
文件名扩展
复制 t.txt 的内容到 t.back 中
vim test22.sh
输入代码:
#!/bin/bash
if [ ! -w 't.txt' ];
then
touch t.txt
fi
echo 'test text' >> t.txt
cp t.{txt,back}
运行代码:
bash test22.sh
查看运行结果:
ls
cat t.txt
cat t.back
注意: 在大括号中,不允许有空白,除非这个空白被引用或转义。
代码块
代码块,又被称为内部组,这个结构事实上创建了一个匿名函数(一个没有名字的函数)。然而,与“标准”函数不同的是,在其中声明的变量,对于脚本其他部分的代码来说还是可见的。
vim test23.sh
输入代码:
#!/bin/bash
{ a=321; }
echo "a = $a"
运行代码:
bash test23.sh
a = 321
变量 a 的值被更改了。
中括号([ ])
条件测试
条件测试表达式放在 [ ] 中。下列练习中的 -lt (less than)表示小于号。
vim test24.sh
输入代码:
#!/bin/bash
a=5
if [ $a -lt 10 ]
then
echo "a: $a"
else
echo 'a>=10'
fi
运行代码:
bash test24.sh
a: 5
数组元素
在一个 array 结构的上下文中,中括号用来引用数组中每个元素的编号。
vim test25.sh
输入代码:
#!/bin/bash
arr=(12 22 32)
arr[0]=10
echo ${arr[0]}
运行代码:
bash test25.sh
10
尖括号(< 和 >)
重定向
test.sh > filename:重定向 test.sh 的输出到文件 filename 中。如果 filename 存在的话,那么将会被覆盖。
test.sh &> filename:重定向 test.sh 的 stdout(标准输出)和 stderr(标准错误)到 filename 中。
test.sh >&2:重定向 test.sh 的 stdout 到 stderr 中。
test.sh >> filename:把 test.sh 的输出追加到文件 filename 中。如果 filename 不存在的话,将会被创建。
竖线(|)
管道
分析前边命令的输出,并将输出作为后边命令的输入。这是一种产生命令链的好方法。
破折号(-)
波浪号(~)
特殊变量
位置参数
从命令行传递到脚本的参数:$0,$1,$2,$3…
$0 就是脚本文件自身的名字,$1 是第一个参数,$2 是第二个参数,$3 是第三个参数,然后是第四个。9 之 后 的 位 置 参 数 就 必 须 用 大 括 号 括 起 来 了 , 比 如 , 9 之后的位置参数就必须用大括号括起来了,比如,9之后的位置参数就必须用大括号括起来了,比如,{10},11 , {11},11,{12}。
$# : 传递到脚本的参数个数
$* : 以一个单字符串显示所有向脚本传递的参数。与位置变量不同,此选项参数可超过 9 个
$$ : 脚本运行的当前进程 ID 号
$! : 后台运行的最后一个进程的进程 ID 号
$@ : 与 $* 相同,但是使用时加引号,并在引号中返回每个参数
$: 显示 shell 使用的当前选项,与 set 命令功能相同
$? : 显示最后命令的退出状态。 0 表示没有错误,其他任何值表明有错误。
位置参数实例
vim test30.sh
1
#!/bin/bash
# 作为用例, 调用这个脚本至少需要10个参数, 比如:
# bash test.sh 1 2 3 4 5 6 7 8 9 10
MINPARAMS=10
echo
echo "The name of this script is \"$0\"."
echo "The name of this script is \"`basename $0`\"."
echo
if [ -n "$1" ] # 测试变量被引用.
then
echo "Parameter #1 is $1" # 需要引用才能够转义"#"
fi
if [ -n "$2" ]
then
echo "Parameter #2 is $2"
fi
if [ -n "${10}" ] # 大于$9的参数必须用{}括起来.
then
echo "Parameter #10 is ${10}"
fi
echo "-----------------------------------"
echo "All the command-line parameters are: "$*""
if [ $# -lt "$MINPARAMS" ]
then
echo
echo "This script needs at least $MINPARAMS command-line arguments!"
fi
echo
exit 0
运行代码:
bash test30.sh 1 2 10
The name of this script is "test.sh".
The name of this script is "test.sh".
Parameter #1 is 1
Parameter #2 is 2
-----------------------------------
All the command-line parameters are: 1 2 10
This script needs at least 10 command-line arguments!
基本运算符
算数运算符
vim test.sh
1
#!/bin/bash
a=10
b=20
val=`expr $a + $b`
echo "a + b : $val"
val=`expr $a - $b`
echo "a - b : $val"
val=`expr $a \* $b`
echo "a * b : $val"
val=`expr $b / $a`
echo "b / a : $val"
val=`expr $b % $a`
echo "b % a : $val"
if [ $a == $b ]
then
echo "a == b"
fi
if [ $a != $b ]
then
echo "a != b"
fi
运行
bash test.sh
a + b : 30
a - b : -10
a * b : 200
b / a : 2
b % a : 0
a != b
原生 bash 不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。
expr 是一款表达式计算工具,使用它能完成表达式的求值操作。
注意使用的反引号(esc 键下边)
表达式和运算符之间要有空格 $a + $b 写成 a + a+a+b 不行
条件表达式要放在方括号之间,并且要有空格 [ a = = b ] 写成 [ b ] 写成 [ b ] 写成 [ a = = a == b ] 写 成 [ b ] 写成 [b]写成[a== a==b]写成[b]写成[b]写成[a==b] 不行
乘号(*)前边必须加反斜杠()才能实现乘法运算
关系运算符
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
#!/bin/bash
a=10
b=20
if [ $a -eq $b ]
then
echo "$a -eq $b : a == b"
else
echo "$a -eq $b: a != b"
fi
逻辑运算符
#!/bin/bash
a=10
b=20
if [[ $a -lt 100 && $b -gt 100 ]]
then
echo "return true"
else
echo "return false"
fi
if [[ $a -lt 100 || $b -gt 100 ]]
then
echo "return true"
else
echo "return false"
fi
字符串运算符
#!/bin/bash
a="abc"
b="efg"
if [ $a = $b ]
then
echo "$a = $b : a == b"
else
echo "$a = $b: a != b"
fi
if [ -n $a ]
then
echo "-n $a : The string length is not 0"
else
echo "-n $a : The string length is 0"
fi
if [ $a ]
then
echo "$a : The string is not empty"
else
echo "$a : The string is empty"
fi
结果
abc = efg: a != b
-n abc : The string length is not 0
abc : The string is not empty
文件测试运算符
实例:
#!/bin/bash
file="/home/shiyanlou/test.sh"
if [ -r $file ]
then
echo "The file is readable"
else
echo "The file is not readable"
fi
if [ -e $file ]
then
echo "File exists"
else
echo "File not exists"
fi
结果
The file is readable
File exists
思考
浮点运算,比如实现求圆的面积和周长。
expr 只能用于整数计算,可以使用 bc 或者 awk 进行浮点数运算。
#!/bin/bash
radius=2.4
pi=3.14159
girth=$(echo "scale=4; 3.14 * 2 * $radius" | bc)
area=$(echo "scale=4; 3.14 * $radius * $radius" | bc)
echo "girth=$girth"
echo "area=$area"
以上代码如果想在环境中运行,需要先安装 bc。
sudo apt-get update
sudo apt-get install bc
流程控制
if else
if
if 语句语法格式:
if condition
then
command1
command2
...
commandN
fi
if else
if else 语法格式:
if condition
then
command1
command2
...
commandN
else
command
fi
if-elif-else 语法格式:
if condition1
then
command1
elif condition2
then
command2
else
commandN
fi
for 循环
for 循环一般格式为:
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
while 语句
while 循环用于不断执行一系列命令,也用于从输入文件中读取数据;命令通常为测试条件。其格式为:
while condition
do
command
done
无限循环
无限循环语法格式:
while :
do
command
done
或者
while true
do
command
done
或者
for (( ; ; ))
until 循环
until 循环执行一系列命令直至条件为真时停止。 until 循环与 while 循环在处理方式上刚好相反。 一般 while 循环优于 until 循环,但在某些时候—也只是极少数情况下,until 循环更加有用。 until 语法格式:
until condition
do
command
done
case
Shell case 语句为多选择语句。可以用 case 语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令。case 语句格式如下:
case 值 in
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
esac
取值后面必须为单词 in,每一模式必须以右括号结束。取值可以为变量或常数。匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;。
取值将检测匹配的每一个模式。一旦模式匹配,则执行完匹配模式相应命令后不再继续其他模式。如果无一匹配模式,使用星号 * 捕获该值,再执行后面的命令。
下面的脚本提示输入 1 到 4,与每一种模式进行匹配:
echo 'Enter a number between 1 and 4:'
echo 'The number you entered is:'
read aNum
case $aNum in
1) echo 'You have chosen 1'
;;
2) echo 'You have chosen 2'
;;
3) echo 'You have chosen 3'
;;
4) echo 'You have chosen 4'
;;
*) echo 'You did not enter a number between 1 and 4'
;;
esac
输入不同的内容,会有不同的结果,例如:
Enter a number between 1 and 4:
The number you entered is:
3
You have chosen 3
跳出循环
在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,Shell 使用两个命令来实现该功能:break 和 continue。
break 命令
break 命令允许跳出所有循环(终止执行后面的所有循环)。
下面的例子中,脚本进入死循环直至用户输入数字大于 5。要跳出这个循环,返回到 shell 提示符下,需要使用 break 命令。
#!/bin/bash
while :
do
echo -n "Enter a number between 1 and 5:"
read aNum
case $aNum in
1|2|3|4|5) echo "The number you entered is $aNum!"
;;
*) echo "The number you entered is not between 1 and 5! game over!"
break
;;
esac
done
执行以上代码,输出结果为:
Enter a number between 1 and 5:3
The number you entered is 3!
Enter a number between 1 and 5:7
The number you entered is not between 1 and 5! game over!
continue
continue 命令与 break 命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。 对上面的例子进行修改:
#!/bin/bash
while :
do
echo -n "Enter a number between 1 and 5: "
read aNum
case $aNum in
1|2|3|4|5) echo "The number you entered is $aNum!"
;;
*) echo "The number you entered is not between 1 and 5!"
continue
echo "game over"
;;
esac
done
运行代码发现,当输入大于 5 的数字时,该例中的循环不会结束,语句 echo “Game is over!” 永远不会被执行。
via:
-
非常好的 BASH 脚本编写教程_bash脚本怎么写-CSDN博客as good as well于 2017-08-04 22:04:28 发布
https://blog.csdn.net/liangshoulong/article/details/76695811 -
Bash Shell 详解:命令、脚本与流程控制-CSDN博客 阿汤哥的程序之路于 2021-05-26 10:21:54 发布
https://blog.csdn.net/tangsiqi130/article/details/117229093 -
高级 Bash 脚本编程指南_Linux - 蓝桥云课
https://www.lanqiao.cn/courses/944