一、构建shell脚本
1、shell脚本
echo $PATH 将shell脚本文件所处的目录添加到PATH环境变量中;在提示符中用绝对或相对文件路径来引用shell脚本文件。
#!/bin/bash 在文件的第一行指定要使用的shell
注释可用#添加
./test1
chmod u+x test1
在环境变量名称之前加上美元符($)来使用这些环境变量。
脚本在引号中出现美元符,它就会以为你在引用一个变量。要显示美元符,你必须在它
前面放置一个反斜线\。
${variable}变量名两侧额外的花括号通常用来帮助识别美元符后的变量名。
用户变量可以是任何由字母、数字或下划线组成的文本字符串,长度不超过20个。
区分大小写,所以变量Var1和变量var1是不同的。
在变量、等号和值之间不能出现空格。
shell脚本会自动决定变量值的数据类型。
引用一个变量值时需要使用美元符,而引用变量来对其进行赋值时则不要使用美元符。
2、命令替换
将命令输出赋给变量:反引号字符 ` 、$()格式
如:testing=`date`、testing=$(date)
赋值等号和命令替换字符之间没有空格。
例:通过命令替换获得当前日期并用它来生成唯一文件名
#!/bin/bash
#copy the /usr/bin directory listing to a log file
today=$(date +%y%m%d)
ls /usr/bin -al > log.$today
+%y%m%d格式告诉date命令将日期显示为两位数的年月日的组合。
3、重定向输入和输出
输出重定向
command > outputfile 创建了一个文件
如果输出文件已经存在了,重定向操作符会用新的文件数据覆盖已有文件。
双大于号(>>)追加数据。
输入重定向
command < inputfile
内联输入重定向,远小于号(<<)
无需使用文件进行重定向,只需要在命令行中指定用于输入重定向的数据就可以了。
指定一个文本标记来划分输入数据的开始和结尾。任何字符串都可作为文本标记,但在数据的开始和结尾文本标记必须一致。
例:
command << marker
data
marker
4、管道
管道连接(piping):将命令输出直接重定向到另一个命令
command1 | command2 数据传输不会用到任何中间文件或缓冲区。
例:$ rpm -qa | sort | more (可停止运行,每次只显示一页)
$ rpm -qa | sort > rpm.list
5、数学运算
将一个数学运算结果赋给某个变量时,可以用美元符和方括号($[ operation ])将数学表达式围起来。
var2=$[$var1 * 2]
bash shell数学运算符只支持整数运算.
浮点解决方案
内建的bash计算器,叫作bc,它允许在命令行中输入浮点表达式,然后解释并计算该表达式,最后返回结果。浮点运算是由内建变量scale控制的。必须将这个值设置为你希望在计算结果中保留的小数位数,否则无法得到期望的结果。
在脚本中使用bc,内联输入重定向,命令替换符号。
例:
#!/bin/bash
var1=10.46
var2=43.67
var3=33.2
var4=71
var5=$(bc << EOF
scale = 4 #小数点保留4位
a1 = ( $var1 * $var2)
b1 = ($var3 * $var4)
a1 + b1
EOF
)
echo The final answer for this mess is $var5
6、退出脚本
shell中运行的每个命令都使用退出状态码(exit status)告诉shell它已经运行完毕。
变量$?来保存上个已执行命令的退出状态码, 一个0~255的整数值
0 命令成功结束
1 一般性未知错误
2 不适合的shell命令
126 命令不可执行
127 没找到命令
128 无效的退出参数
128+x 与Linux信号x相关的严重错误
130 通过Ctrl+C终止的命令
255 正常范围之外的退出状态码
默认情况下,shell脚本会以脚本中的最后一个命令的退出状态码退出。
exit 5 脚本结束时指定一个退出状态码$?
shell通过模运算得到退出状态码数值。
创建脚本的最基本的方式是将命令行中的多个命令通过分号分开来。shell会按顺序逐个执行命令,在显示器上显示每个命令的输出。
$ date ; who
二、结构化命令(改变程序执行的顺序)
1、使用if-then 语句
使用if-then 语句
if command if语句会运行if后面的那个命令
then
commands if 命令退出状态码是0,位于then部分的命令会被执行;否则不执行
fi fi语句用来表示if-then语句到此结束
若if语句中的那个命令错误,生成的错误消息依然会显示在脚本的输出中。
if command; then
commands
fi
使用if-then-else 语句
if command
then
commands 当if语句中的命令返回退出状态码0时,then部分中的命令会被执行
else
commands 当if语句中的命令返回非零退出状态码时, else部分中的命令会执行
fi
嵌套if
可以使用else部分的另一种形式:elif
if command1
then
commands
elif command2
then
more commands
fi
记住,在elif语句中,紧跟其后的else语句属于elif代码块。
if command1
then
command set 1
elif command2
then
command set 2
elif command3
then
command set 3
fi
2、test命令
if-then语句不能测试命令退出状态码之外的条件,加上test可以。
if test condition 如果test命令中列出的条件成立,test命令就否则就会报错
then
commands
fi
bash shell只能计算整数,可echo浮点数
test命令可以判断三类条件:
- 数值比较
- 字符串比较
- 文件比较
if [ condition ] 第一个方括号之后和第二个方括号之前必须加上一个空格,与test同效
then
commands
fi
复合条件测试
[ condition1 ] && [ condition2 ]
[ condition1 ] || [ condition2 ]
(( expression )) 任意的数学赋值或比较表达式
[[ expression ]] 字符串比较
case variable in
pattern1 | pattern2) commands1;;
pattern3) commands2;;
*) default commands;;
esac
3、循环
3.1
for var in list
do
commands
done
每次for命令遍历值列表,它都会将列表中的下个值赋给$ var变量
使用转义字符(反斜线 \ )来将单引号转义;
使用双引号来定义用到单引号的值、包含空格的数据值。
从变量读取列表,从命令读取值、用通配符读取目录
字段分隔符:空格、制表符、换行符
IFS.OLD=$IFS
IFS=$'\n' 修改IFS的值,使其只能识别换行符
IFS=$'\n':;" 这个赋值会将换行符、冒号、分号和双引号作为字段分隔符。
3.2 C语言风格for命令
for (( a = 1; a < 10; a++ ))
do
commands
done
变量赋值可以有空格;条件中的变量不以美元符开头;迭代过程的算式未用expr命令格式。
for (( a=1, b=10; a <= 10; a++, b-- ))
可以使用多个变量,但只能在for循环中定义一种条件。
3.3 while命令
先判断test条件,再运行do
while test command 返回的是退出状态码0。它 可用中括号
do
other commands
done
3.4 until
until test commands
do
other commands
done
测试命令的退出状态码为0,bash shell才会跳出循环
3.6循环处理文件数据
使用嵌套循环
修改IFS环境变量
3.7 控制循环
break命令、continue命令
①break跳出(停止)单个循环,跳出当前正在执行的循环
②break跳出(停止)内部循环,在处理多个循环时,break命令会自动终止你所在的最内层的循环。
③break跳出(停止)外部循环, break n,n指定了要跳出的循环层级,n为1——跳出的是当前的循环,n设为2——停止下一级的外部循环
④continue命令可以提前中止某次循环中的命令,但并不会完全终止整个循环。在循环
内部设置shell不执行命令的条件。
⑤continue n其中n定义了要继续的循环层级
3.8循环的输出重定向与使用管道
done > output.txt
done > test23.txt
done | sort
3.9 实例
①查找可执行文件 $PATH
#!/bin/bash
#finding files in the PATH
IFS=:
for folder in $PATH
do
echo "$folder:"
for file in $folder/*
do
if [ -x $file ]
then
echo " $file"
fi
done
done
$ ./test25 | more
/usr/local/bin:
/usr/bin:
/usr/bin/Mail
/usr/bin/Thunar
/usr/bin/X
…
②创建多个用户账户
userid,user name
两个值之间使用逗号分隔,形成一种名为逗号分隔值的文件格式(或者是.csv), 在电子表格中极其常见.。可在电子表格程序中创建用户账户列表,然后将其保存成.csv格式,shell可读取处理。
#!/bin/bash
#process new user accounts
input="users.csv"
while IFS=',' read -r userid name
do
echo "adding $userid"
useradd -c "$name" -m $userid
done < "$input"
read命令会自动读取.csv文本文件的下一行内容。当read命令返回FALSE时(也就是读取完整个文件时),while命令就会退出。
三、处理用户输入
1、命令行参数
$ ./addem 10 30
位置参数:$0是程序名,$1是第一个参数,$2是第二个参数,依次类推,直到第九个参数$9。命令行参数超过9时,需在变量数字周围加上花括号{},如${10}
shell脚本会自动将命令行参数的值分配给变量,不需要作任何处理。
参数值中包含空格,必须要用引号(单引号或双引号均可)。
读取脚本名,basename命令会返回不包含 路径 与 ./ 的脚本名。
name=$(basename $0)
在shell脚本中使用命令行参数时,如果脚本不加参数运行,可能会产生错误消息。
检查其中是否存在数据:
if [ -n "$1" ]
2、特殊参数变量
参数统计
$#含有脚本运行时携带的命令行参数的个数($1开始计数),无需使用-n确认是否有参数
获取命令行中最后一个参数:${!#}
$#的值为0时,${!#}变量会返回命令行用到的脚本名
抓取所有的数据
$ *和$ @都能够在单个变量中存储所有的命令行参数。
$ *变量会将命令行上提供的所有参数当作一个单词保存。
$ @变量会将命令行上提供的所有参数当作同一字符串中的多个独立的单词,这样就能够遍历所有的参数值。for param in "$@"
3、移动变量
使用shift命令,默认情况下它会将每个参数变量向左移动一个位置。
变量$2的值会移到$1中,而变量$1的值则会被删除(注意,变量$0的值,也就是程序名,不会改变)。
(参数数量未知时,可以只操作第一个参数,移动参数,然后继续操作第一个参数。)
count=1
while [ -n "$1" ]
do
echo "Parameter #$count = $1"
count=$[ $count + 1 ]
shift
done
如果某个参数被移出,它的值就被丢弃了,无法再恢复。
shift n 一次性移动多个位置
4、处理选项
4.1 查找选项
①处理简单选项
通过shift移动命令行选项,case判断谋个参数是否为选项
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) echo "Found the -b option" ;;
-c) echo "Found the -c option" ;;
*) echo "$1 is not an option" ;;
esac
shift
done
②分离参数和选项
双破折号(——)将命令行选项和参数分开
#!/bin/bash
#extracting options and parameters
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) echo "Found the -b option";;
-c) echo "Found the -c option" ;;
--) shift
break ;;
*) echo "$1 is not an option";;
esac
shift
done
#
count=1
for param in $@
do
echo "Parameter #$count: $param"
count=$[ $count + 1 ]
done
$ ./test16.sh -c -a -b -- test1 test2 test3
③处理带值的选项
#!/bin/bash
#extracting command line options and values
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option";;
-b) param="$2"
echo "Found the -b option, with parameter value $param"
shift ;;
-c) echo "Found the -c option";;
--) shift
break ;;
*) echo "$1 is not an option";;
esac
shift
done
#
count=1
for param in "$@"
do
echo "Parameter #$count: $param"
count=$ [ $count + 1 ]
done
$ ./test17.sh -a -b test1 -d
Found the -a option
Found the -b option, with parameter value test1
-d is not an option
4.2 使用getopt命令
①getopt命令格式
getopt命令可以接受一系列任意形式的命令行选项和参数,并自动将它们转换成适当的格式。
getopt optstring parameters
$ getopt ab:cd -a -b test1 -cd test2 test3
-a -b test1 -c -d -- test2 test3
在optstring中列出你要在脚本中用到的每个命令行选项字母,在每个需要参数值的选项字母后加一个冒号。getopt会自动将-cd选项分成两个单独的选项,并插入双破折线来分隔行中的额外参数。
忽略错误消息,可以在命令后加-q选项。
$ getopt -q ab:cd -a -b test1 -cde test2 test3
-a -b 'test1' -c -d -- 'test2' 'test3'
②在脚本中使用getopt命令
用getopt命令生成的格式化后的版本来替换已有的命令行选项和参数。用set命令能够做到。
set命令的选项之一是双破折线(--),它会将命令行参数替换成set命令的命令行值。
set -- $(getopt -q ab:cd "$@")
getopt命令并不擅长处理带空格和引号的参数值。
4.3 高级getopts
getopts包含getopt的功能与更高级的功能。
每次调用它时,它一次只处理命令行上检测到的一个参数。处理完所有的参数后,它会退出并返回一个大于0的退出状态码。适用于解析所有参数的循环。
getopts optstring variable
要去掉错误消息的话,可以在optstring之前加一个冒号。getopts命令将当前选项保存在命令行中定义的variable中。
如果选项需要跟一个参数值,OPTARG环境变量就会保存这个跟随参数值;OPTIND环境变量保存了参数列表中getopts下一个要处理的元素位置。
while getopts :ab:c opt
do
case "$opt" in
a) echo "Found the -a option" ;;
b) echo "Found the -b option, with value $OPTARG";;
c) echo "Found the -c option" ;;
*) echo "Unknown option: $opt";;
esac
done
getopts命令解析命令行选项时会移除开头的单破折线,所以在case定义中不用单破折线。
①getopts可以在参数值中包含空格。 $ ./test19.sh -b "test1 test2" -a
②将选项字母和参数值放在一起使用,而不用加空格。$ ./test19.sh -abtest1
③能将命令行上找到的所有未定义的选项统一输出成问号。
OPTIND环境变量保存了参数列表中getopts下一个要处理的元素位置。
sh 1.sh -ab foo
-ab 第一个元素,OPTIND=1
foo 第2个元素, OPTIND=2
第3个元素为空 OPTIND=3
所以处理-a时,下一个是b,依然在处理第一个元素,所以OPTIND =1
处理b时,下一个是foo,但foo不是参数SKIP掉了,下一个元素是空,OPTIND =3
所以打印OPTIND =3
4.4 选项标准化(常用的Linux命令选项)
-a 显示所有对象
-c 生成一个计数
-d 指定一个目录
-e 扩展一个对象
-f 指定读入数据的文件
-h 显示命令的帮助信息
-i 忽略文本大小写
-l 产生输出的长格式版本
-n 使用非交互模式(批处理)
-o 将所有输出重定向到的指定的输出文件
-q 以安静模式运行
-r 递归地处理目录和文件
-s 以安静模式运行
-v 生成详细输出
-x 排除某个对象
-y 对所有问题回答yes
4.5 获取用户输入
①基本读取:
read命令从标准输入(键盘)或另一个文件描述符中接受输入,将输入数据放进一个变量
echo -n "Enter your name: " -n允许脚本用户紧跟其后输入数据,而不是下一行
read name
read -p "Please enter your age: " age -p允许你直接在read命令行指定提示符
read输入的每个数据值都会分配给变量列表中的下一个变量。如果变量数量不够,剩下的数据就全部分配给最后一个变量。
若read命令不指定变量,read命令会将它收到的任何数据都放进特殊环境变量REPLY中。
②超时
-t选项指定一个计时器,指定了read命令等待输入的秒数。当计时器过期后,read命令会返回一个非零退出状态码。-t 5
可以使用如if-then语句或while循环这种标准的结构化语句来理清所发生的具体情况
可以不对输入过程计时,而是让read命令来统计输入的字符数。当输入的字符达到预设
的字符数时,就自动退出
read -n1 -p "Do you want to continue [Y/N]? " answer 接受单个字符传给变量后退出
③隐藏方式读取
-s 避免在read命令中输入的数据出现在显示器上,但会赋给变量,以便在脚本中使用
④从文件中读取
每次调用read命令,它都会从文件中读取一行文本。当文件中再没有内容时,read命令会退出并返回非零退出状态码。
将文件中的数据传给read命令:对文件使用cat命令,将结果通过管道直接传给含有read命令的while命令。
cat test | while read line
四、呈现数据
1、标准文件描述符
①文件描述符 缩 写 描 述
0 STDIN 标准输入
1 STDOUT 标准输出
2 STDERR 标准错误
②重定向输入、输出、错误
重定向错误
$ ls -al badfile 2> test4
重定向输出、错误
ls -al test test2 test3 badtest 2> test6 1> test7
STDERR和STDOUT的输出重定向到同一个输出文件, 特殊的重定向符号&>
ls -al test test2 test3 badtest &> test7
2、在脚本中重定向输出、错误
①临时重定向
$ cat test8
#!/bin/bash
# testing STDERR messages
echo "This is an error" >&2 使用输出重定向符来将输出信息重定向到STDERR文件描述符
echo "This is normal output"
$ ./test8 2> test9
This is normal output 脚本中所有导向STDERR的文本都会被重定向
$ cat test9
This is an error
②永久重定向
脚本中有大量数据需要重定向,用exec命令告诉shell在脚本执行期间重定向某个特定文件描述符。
$ cat test10
#!/bin/bash
# redirecting all output to a file
exec 1>testout 将STDOUT文件描述符重定向到文件
exec 2>testerror STDERR的输出重定向到文件testerror
echo "This is a test of redirecting all output"
echo "from a script to another file."
echo "without having to redirect every individual line"
echo "but this should go to the testerror file" >&2
3、脚本中重定向输入
$ cat test12
#!/bin/bash
# redirecting file input
exec 0< testfile 从文件testfile中获得输入
count=1
while read line
do
echo "Line #$count: $line"
count=$[ $count + 1 ]
done
$ ./test12
Line #1: This is the first line.
Line #2: This is the second line.
Line #3: This is the third line.
4、创建自己的重定向
从3~8的文件描述符均可用作输入或输出重定向
①创建输出文件描述符
exec 3>test13out 可以在显示器上保持正常的输出,而将特定信息重定向到文件中
echo "and this should be stored in the file" >&3
echo "Then this should be back on the monitor"
exec 3>>test13out 现在输出会被追加到test13out文件
②重定向输出、输入文件描述符(用于恢复STDOUT、STDIN指向的原来位置)
exec 3>&1 文件描述符3的输出都将出现在显示器上
exec 1>test14out STDOUT的输出直接重定向到输出文件中
exec 1>&3 STDOUT的输出指向原来位置(显示器)
exec 6<&0
exec 0< testfile
exec 0<&6
③关闭文件描述符
如果你创建了新的输入或输出文件描述符,shell会在脚本退出时自动关闭它们。
手动关闭文件描述符 exec 3>&-
五、shell 命令
1、export 设置或显示环境变量
语 法:export [-fnp][变量名称]=[变量设置值]
补充说明:在shell中执行程序时,shell会提供一组环境变量。export可新增,修改或删除环境变量,供后续执行的程序使用。export的效力仅限于该次登陆操作。
参 数:
-f 代表[变量名称]中为函数名称。
-n 删除指定的变量。变量实际上并未删除,只是不会输出到后续指令的执行环境中。
-p 列出所有的shell赋予程序的环境变量。
结 论:
1.1 执行脚本时是在一个子shell环境运行的,脚本执行完后该子shell自动退出;
1.2 一个shell中的系统环境变量会被复制到子shell中(用export定义的变量);
1.3 一个shell中的系统环境变量只对该shell或者它的子shell有效,该shell结束时变量消失(并不能返回到父shell中)。
1.4 不用export定义的变量只对该shell有效,对子shell也是无效的。
问:为什么一个脚本直接执行和用source执行不一样呢?
直接执行一个脚本文件是在一个子shell中运行的,而source则是在当前shell环境中运行的。
2、source 执行指定脚本文件
source:source命令是一条用于执行指定脚本文件的命令。它可以将文件中定义的变量、函数和别名加载到当前shell环境中。
使用方法:source 文件路径 或 . 文件路径
使用这条命令可以在当前shell中加载外部文件的内容,而不是在新的子shell中运行脚本。这样可以确保脚本中定义的变量、函数和别名对当前shell环境可见,而不仅仅在子shell中有效。
source命令也可以用于在当前shell中重新加载运行时已经修改过的脚本文件,以便更新环境变量和函数定义。