Shell
基础脚本
使用多个命令
- date ; who
创建脚本文件
$ vi/vim test1
#!/bin/bash
# This is a test file.
date
who
修改文件权限
首先查看文件权限
- ls -l test1
修改文件权限
umask值决定了新文件的默认权限设置
- chmod u+x test1
执行文件
./test1
直接执行:test1
由于test1不在PATH中,需要执行./test1命令
编写脚本或直接使用echo时,要输出一些符号,在他们两侧使用"",脚本中可以在环境变量前加$使用它们。要使用一些特殊符号,则需要加转义符\
使用变量
环境变量
set命令可以显示一张完整的环境变量表。
对于$符号,修改就不加,使用就加上。
用户变量
使用例子:
var1=10
var2=-57
var3=testing
var4="still more testing"
var5=$var4
命令替换
有两种方法可以将命令输出赋给变量:
反引号 " ` "
$ ( ) 格式
要么用一对反引号把整个命令行围起来 testing='date'
要么使用$ ( ) 格式 testing=$(date)
数学运算
expr命令
expr命令只能识别少数的数学和字符串操作符。而且使用起来较为复杂。
使用方括号[]
可以使用$[运算]进行操作。使用起来也比expr方便得多。
bc
可以在shell提示符下通过bc命令访问bash计算器。
浮点数内建变量时scale控制的,必须将它设置为你希望保留的小数位数才能得到期望的结果。默认值为0。
脚本中使用bc
基本格式:variable=$(echo "options ; expression" | bc)
实例:
#!/bin/bash
var1=$(echo "scale=4 ; 3.44 / 5" | bc)
echo The answer is $var1
如果要进行大量运算,最好是在shell脚本中使用重定向输入
variable=$(bc << EOF
options
expressions
EOF
)
退出脚本
退出状态码
Linux提供了一个专门的变量$?来保存上个已执行命令的退出状态码。
按照惯例,一个成功结束的命令的退出状态码是0.如果一个命令结束时有错误,退出状态码就是一个正常值。
Linux错误退出状态码没有什么标准可循,但是有一些可用的参考。
exit命令
一般状态下,shell脚本会以最后一个命令的退出状态码退出。
但exit命令允许你在脚本结束时指定一个退出状态码。
如果你的设置大于255,则结果会被除余256.
结构化命令
if语句
if-then
-格式:
if command then commands fi
-说明:
bash shell的if语句会运行后面的那个命令。如果该命令的退出状态码为0,则执行then,否则跳过then。fi语句表示if-then语句到此结束。
-实例:$ cat test2.sh #!/bin/bash # testing a command if ImNotaCommand //不能工作的命令 then echo "It worked" fi echo "We are outside of the if"
if-then-else
-格式:
if command then commands else commands fi
-说明:
if为0执行then,非0执行else。if-then-elif-then
-格式:
if command1 then commands elif command2 then
嵌套if
-实例:
testusr=NoSuchUser if grep $testusr /etc/passwd then echo "The user $testusr exists." else echo "The user $testusr doesn't exits." if ls -d /home/$testusr/ then echo "However, $testusr has a directory." fi fi
高级特性
- 双括号(( expression ))
允许你在比较过程中使用高级数学表达式 - 双方括号[[ expression ]]
双方括号的表达式使用了test命令中采用的标准字符串比较。提供了test命令未提供的模式匹配特性(正则表达式)。
- 双括号(( expression ))
test
test可以测试退出状态码之外的条件,如果test命令中列出的测试条件成立,test命令就会返回退出状态码0。
此外在test中大写字母被认为是小于小写字母的,与sort命令相反。
数值比较
- 在if后加 [ 表达式 ]
- a -eq b a=b?
- a -ge b a>=b?
- a -gt b a>b?
- a -le b a<=b?
- a -lt b a<b?
- a -ne b a!=b?
字符串比较
- 在if后加 [$a = $b ]
- a = b
- a != b
- a < b 短
- a > b 长
- -n a 长度非0?
- -z a 长度是0?
- 使用大小于号时要加\转义,否则判定为重定向。
文件比较
- -d file file为目录?
- -e file file存在?
- -f file file是文件?
- -r file file存在并可读?
- -s file file存在并非空?
- -w file file存在并可写?
- -x file file存在并可执行?
- -O file file存在并属于当前用户?
- -G file file存在并且默认组与当前用户相同
- file1 -nt file2 file1比file2新?
- file1 -ot file2 file1比file2旧?
复合条件测试
- [] && []
- [] || []
case
与其他编程语言的case作用相同。
-格式:
case variable in
pattern1 | pattern2) commands1;;
pattern3) commands2;;
*) default commands;;
esac
for
-格式:
for var in list
do
commands
done
-实例1:
for test in A B C D E F
do
echo The next state is $test
done
-实例2:
file="test1"
for state in $(cat $file)
do
echo "Visit $state"
done
- 支持C语言格式for
for (( a=1, b=2; a<10; a++, b--))
while
while test command
do
commands
done
until
until与while工作方式完全相反。
until要求你指定一个通常返回非零退出状态码的命令。
-格式:
until test commands
do
commands
done
嵌套循环
可使用for、while、until等循环嵌套
控制循环
break
跳出单个循环
如果要跳出多层循环:
brean n
n为循环的层数continue
提前终止一次循环。
循环处理文件数据
· 使用嵌套循环
· 修改IFS环境变量
修改IFS后就能强制for命令将文件的每一行都当成单独的一个条目处理。一旦从文件中提取出了单独的行,可能需要再次利用循环来提取行的数据。
-实例:
#!/bin/bash
# changing the IFS value
IFS.OLD=$IFS
IFS=$'\n'
for entry in $(cat /etc/passwd)
do
echo "Values in $entry -"
IFS=:
for value in $entry
do
echo " $value"
done
done
处理循环输出
在done后加上重定向
done -> ouput.txt
注意事项
在读取文件名时,最好进行""处理,否则若文件名包含空格,shell会当成参数处理。
-实例:if [ -d "$file" ]
test命令shell也会把额外的单词作为参数处理,造成错误。
实战1:查找可执行文件
脚本代码
#!/bin/bash # finding files in the PATH IFS=: dor folder in $PATH do echo "$folder:" for file in $folder/* do if [ -x $file ] then echo " $file" fi done done
实战2:创建多个用户帐户
shell脚本的目标是让系统管理员过的更轻松。如果需要创建大量的用户账户,可以使用while来降低工作难度。
脚本代码
#!/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"
处理用户输入
命令行参数
向脚本能传递数据最基本的方法是使用参数。
-实例: $ ./addem 10 30
读取参数
bash shell会将一些称为位置参数的特殊变量分配给输入到命令行中的所有参数:
$0:程序名 $1:第一个参数....直到第九个参数$9读取脚本名
可以用$0参数获取shell在命令行启动的脚本名。
test5sh :#!/bin/bash # testing the $0 parameters echo The zero parameter is set to: $0
但有一个潜在的问题:如果使用另一个命令运行shell脚本,命令和脚本名混在一起出现在$0参数。
$ ./test5.sh
The zero parameter is set to: ./test5.sh
当传给$0变量的实际字符串不仅仅是脚本名,而是完整的脚本路径时,$0就会使用整个路径。The zero parameter is set to: /home/henry/test5.sh
如果要编写一个根据脚本名来执行不同功能的脚本,就得做些额外工作。
basename会返回不包含路径的脚本名。#!/bin/bash # using basename with the $0 parameter name=$(basename $0) echo echo The script name is: $name
测试参数
在使用命令行参数时要小心,如果脚本不加参数允许,可能会出问题。
可以检查:[ -n "$1" ]
特殊参数变量
参数统计:$#含有脚本运行时携带的命令行参数个数
还提供了简洁的一个方法获取了另外一个参数:
方法1:params=$# echo The last parameter is $params echo The last parameter is ${!#}
抓取所有数据:$*和$@变量可以访问所有参数
$*将所有参数作为一个单词保存。
$@将所有参数当作同一字符串中的多个独立单词。可使用for遍历。
移动变量
shift:将每个参数左移一位,$1会被删除
shift n一次移动n个位置。
处理选项
查找选项
- 处理简单选项
#!/bin/bash # extracting command line options as parameters echo while [ -n "$1" ] do case "$1" in -a) echo "Found the "$1" option" ;; -b) echo "Found the "$1" option" ;; -c) echo "Found the "$1" option" ;; *) echo "$1 is not an option" ;; esac shift done
分离参数和选项
Linux在处理同时使用参数和选项的时候,采用了特殊字符来将二者分开。
对于Linux来说,特殊字符是双破折线( -- )。shell会使用双破折现来表明选项列表结束。- 处理带值的选项
getopt命令
getopt命令可以接受一系列任意形式的命令行选项和参数,并自动将它们转化成适当的格式。
getopts
getopts能和已有的shell参数变量配合默契。
每次调用,都只处理命令行上检测到的一个参数。getopts命令知道何时停止处理选项,并将参数留给你处理。getopts处理每个选项的时间,OPTIND环境变量+1,在命令完成处理时,你可以使用shift和OPTIND值移动参数。
-实例:#!/bin/bash # Processing options ¶meters with getopts echo while getopts :ab:cd opt do case "$opt" in a) echo "Found the -a options" ;; b) echo "Found the -b options, with value $OPTARG" ;; c) echo "Found the -c options" ;; d) echo "Found the -d options" ;; *) echo "Unknown option: $opt" ;; esac done shift $[ $OPTIND - 1 ] echo count=1 for param in "$0" do echo "Parameter $count: $param" count=$[ $count + 1 ] done
获得用户输入
基本读取:read
read命令会从标准输入( 键盘 )或另一个文件描述符中接受输入。接收后,会将数据放进一个变量。
read variable # = input()
与编程语言中的input比较像read -p "input" variable // = input("input")
如果不指定变量,read会将接收到的所有数据放入特殊环境变量REPLY中。超时
使用read时,需要注意脚本可能一直在等待用户的输入。
如果不管是否有数据都需要执行脚本,则可以使用read -t n选项
可以设置计时器秒数n。超时会返回非零退出状态码。隐藏方式读取
需要输入但不能显示在显示器上的内容,比如密码。
read -s从文件读取
常见的方法是对文件使用cat命令,将结果通过管道传给有read命令的while命令。
cat test | while read line do .... done
呈现数据
理解输入输出
标准文件描述符
Linux用文件描述符来标识每个文件对象。文件描述符是一个非负整数,可以唯一标识会话中打开的文件。
出于特殊目的,bash shell只保留了前三个文件描述符(0、1和2)
0 STDIN 标准输入
1 STDOUT 标准输出
2 STDERR 标准错误- STDIN
代表shell的标准输入,对于终端来说,标准输入是键盘。 - STDOUT
shell标准输出,终端标准输处时显示器。 - STDERR
shell中或者脚本出错时生成的错误消息都会发送到这个位置。 - 重定向错误
只重定向错误
STDERR被设定为2。可以选择只重定向错误消息,将该文件描述符值放在重定向符号前。
-实例:
ls -al badfile 2> test4
运行该命令,错误消息不会出现在屏幕上了。重定向错误和数据
如果想重定向错误和正常输出,必须使用两个重定向符号。
-实例:
$ ls -al test test2 test3 badtest 2> test6 1> test7 $ cat test 6 ls: cannot access test: No such file or directory ls: cannot access badtest: No such file or directory $ cat test7 -rw-rw-r-- 1 rich rich 158 2023-07-28 14:56 test2 -rw-rw-r-- 1 rich rich 0 2023-07-28 14:57 test3
- STDIN
如果你愿意将STDERR和STDOUT输出重定向到同一个文件,可以使用&>
脚本中重定向输出
临时重定向
如果要在脚本中输出错误消息。需要使用重定向符将输出信息重定向道STDERR文件描述符。在重定向道文件描述符时,必须加一个&
echo "This is an error message" >&2
默认情况下,Linux会将STDERR导向STDOUT。但在运行脚本时重定向了STDERR,脚本所有STDERR文本都会被重定向。永久重定向
如果脚本内有大量数据需要重定向,那重定向每个echo语句会很繁琐。
可以使用exec告诉shell脚本执行期间重定向道某个特定文件的文件描述符。
exec 2>testerror
exec 1>testout
脚本中重定向输入
- exec 0< testfile
创建自己的重定向
脚本的重定向输入输出不局限于这3个默认的文件描述符。shell中最多可以有9个打开的文件描述符。
创建输出文件描述符
exec命令可以给输出分配文件
-实例:
exec 3>testout
追加:
exec 3>>testout- 重定向文件描述符
$ cat test10
#!/bin/bash
# storing STDOUT, then coming back to it.
exec 3>&1
exec 1>test10out
echo "This should store in the output file"
exec 1>&3
echo "Now things should be back to normal"
创建输入文件描述符
与重定向输出相同
创建读写文件描述符
了解文件读取时的指针,小心使用
exec 3<> testfile关闭文件描述符
一般脚本退出时会自动关闭,但某些情况下需要手动关闭。
exec 3>&-
关闭后就不能在脚本中向它写入任何信息,否则就会生成错误消息。
列出打开的文件描述符
如果你忘记了重定向符定到了哪,可以使用lsof命令。
指定进程ID(PID):-p
指定要显示的文件描述符编号:-d
对两个选项的结果执行布尔AND运算:-a
想知道进程当前的PID可以使用特殊环境变量$$(shell会将它设为当前PID)
-实例: $ /usr/sbin/lsof -a -p $$ -d 0,1,2
阻止命令输出
如果你不想显示任何脚本的输出。可以将STDERR重定向到null的特殊文件,位置为/dev/null,任何数据都不会被保存。
创建临时文件
/tmp文件会在开机重启后清空
本地临时文件
默认情况下,mktemp可以在/tmp目录中创建一个唯一的临时文件。shell会创建这个文件,但不会使用默认umask值。会将文件的读写权限分配给文件属主,并将你设为属主。
在创建一个临时文件时只要制定一个模板即可,可以包含任何文本文件名,在文件名末尾加上6个X即可。
mktemp testing.XXXXXX
在创建完后保存文件名进行使用。在/tmp目录创建临时文件
-t选项会强制mktemp命令来在系统的临时目录来创建文件。mktemp会返回创建临时文件的全路径,不仅仅有文件名。
创建临时目录
-d选项
记录消息
将输出同时发送到显示器和日志文件,而不必重定向两次。
tee命令相当于管道的一个T型接头。但每次会覆盖文件,追加:-a
-实例:
$ date | tee testfile
Sun Ocy 19 18:56:12 EDT 2014
$ cat testfile
Sun Ocy 19 18:56:12 EDT 2014
实例
从.csv读取文件,创建INSERT语句将数据插入MySQL数据库
$ cat test11
#!/bin/bash
# read file and create INSERT statements for MySQL
outfile='members.sql'
INF=','
while read lname fname address city state zip
do
cat >> $outfile << EOF
INSERT INFO members ( lname, fname, address, city, state, zip ) VALUES
('$lname', '$fname', '$address', '$city', '$state', '$zip');
EOF
done < ${1}
控制脚本
处理信号
Linux系统信号
Linux系统和应用程序可以生成超过30个信号。有不同功能。
生成信号
- 中断进程:Ctrl+C
- 暂停进程:Ctrl+Z
捕获信号:trap
-命令允许指定shell脚本要监看并从shell中拦截的Linux信号。
--格式:
trap commands signals
-捕获脚本退出
trap EXIT
-修改或者移除捕获
想要在脚本中不同位置进行不同捕获处理,重新使用新参数的trap即可。
--实例:
trap "echo 'Ctrl-C is trapped.'" SIGINT
脚本即无法终止,想要恢复,参数设置为两个--即可
后台模式
以后台模式运行
只需要在后面加上个&。
-实例:
./test4.sh &
但输出输入命令仍会在前台显示,最好将STDOUT和STDERR重定向。运行多个后台作业
可以连续使用多次&,通过ps可以查看运行状态。
同时应该注意,每一个后台进程都和终端会话(pts/0)联系在一起。
在非控制台下运行脚本
有时候你希望即使退出控制台也能继续运行脚本,可以使用nohup实现。同时输出的消息保存至nohup.out
作业控制
在作业停止后,Linux会让你选择终止还是重启。终止:kill、重启:发送SIGCONT信号。
启动、停止、终止、恢复作业的这些方式都称为作业控制。
查看作业:jobs
-l 列出PID以及作业号
-n 只列出上次shell通知后改变状态的作业
-p 只看PID
-r 只看运行中
-s 只看已停止重启停止作业:bg
-格式:
bg <作业号>
要是以前台模式重启作业:
fg <作业号>
调整谦让度
多任务操作系统(Linux就是)中,存在优先级轮转。shell启动的所有进程的调度优先级默认都相同。
调度优先级为整数,从-20(最高优先)到+19(最低优先)。默认情况为0。
nice
允许设置命令启动时的优先级。
-实例:
nice -n 10 ./test4.sh > test4.out &
但nice命令组织用户系统提高命令优先级,作业会运行,但是调整优先级会失败。renice
改变系统上已运行命令的优先级。
-实例:
renice -n 10 -p 5055
定时运行
at
-格式:
at [-f filename] time
默认情况下,at会将STDIN的输入放到队列中。你可以使用-f参数注定用于读取的命令(脚本文件)的文件名。
time的格式可以自己查获取作业
最好在脚本中进行重定向输出
列出等待作业
atq命令可以查看系统中有哪些作业在等待
删除作业
atrm <作业号>
定期执行
cron时间表
Linux基础中已经描述
重定向输入、输出、管道
Linux中以写明,不在赘述
Xmind文件:
Shell.xmind