Chap3 Linux Shell编程入门
Shell的工作原理
Shell:用户和操作系统之间的接口。
当用户登录到系统时,有一个shell进程随之启动,并在用户注销时终止。
用户输入的命令时shell的输入。
shell处理命令的步骤:①查找命令中的元字符 ②把这些元字符替换成对应的实际操作参数 ③将重新生成的指令传给内核执行 ④等待指令完成,提示符重新出现,等待下一条命令。
Shell种类:
• Bourne系列:
• Bourne Shell (/bin/sh) • Korn Shell (/bin/ksh) • Bash (/bin/bash)
• C Shell系列:
• C Shell (/bin/csh) • Tcsh (/bin/tcsh)
Shell命令元字符
通配符:用来表示文件名的某种模式,在解释时被替换成其他字符。
通配符 | 匹配内容 |
* | 任意数量的任意字符 |
? | 单个任意字符 |
[abc] | a,b,c中的任一个字符 |
[a-z] | ASCII码值在a与z中间的任一个字符 |
[!a-z] | 不在a-z范围内的任一个字符 |
!(fname) | 除fname之外的所有文件名 |
!(f1|f2) | 除f1和f2之外的所有文件名 |
ls .??* //.开头(隐藏文件),≥两个字符长度的文件名
rm *.c //删除以.c结尾的所有文件
rm * .c //删除所有文件(等价于rm *)
ls c*[!0-9] //以c开头,长度任意,结尾不为数字
cp file f[1-3] //cp命令多个参数时默认最后一个参数是一个目录,否则错误
cp f[1-3] file //等价于cp f1 f2 f3 file
转义符:在处理文件名中出现通配符的文件时,常常需要用转义符来防止通配符起作用。
①在通配符前使用一个\
\*
\?
\[ \]
\[Enter] 换行继续输入
②使用引号
双引号:禁止通配符替换
单引号:禁止通配符、变量名和命令替换
Shell脚本
命令替换:
①反引号`命令`:使用命令的结果来替换命令。反引号中的被视作命令,输出命令执行结果,相当于调用子程序。
②$(命令):整个替换成括号中命令的结果
echo –e "The user list is \n `who`"
echo The current time is `date`
echo The current time is $(date)
重定向:
命令的输入输出流:
输入和输出终端:键盘、显示器
命令的输入和输出字符流:
标准输入流:连接到见键盘的输入
标准输出流:连接到显示器的输出
标准错误流:连接到显示器的错误消息输出
三种标准输入输出源:终端、文件、管道
输入输出重定向到文件:
文件描述符 | 含义 |
0 | 标准输入 |
1 | 标准输出 |
2 | 标准错误 |
3... | 其他文件 |
- 标准输入(从键盘输入)
cat -foo
> 输出重定向(覆盖)(为1>简写)
echo content > file
>> 追加输出重定向
echo content >> file
< 输入重定向(为1<简写)
wc < file
2> 标准错误输出重定向
cat foo 2> errorfile
相互重定向
1>&2 //标准输出流放入标准错误流
2>&1 //标准错误流放入标准输出流
过滤器:同时用到输入和输出流的命令。对于过滤器来说,输入输出流的顺序是不重要的。
wc < infile > outfile
wc > outfile < infile
>outfile < infile wc
wh1516@PinkRAY:~/桌面$ touch f1
wh1516@PinkRAY:~/桌面$ echo f1giaof1 >f1
wh1516@PinkRAY:~/桌面$ echo f2giaof2 >f2
wh1516@PinkRAY:~/桌面$ touch f3
wh1516@PinkRAY:~/桌面$ cat - f2 < f1 >f3
wh1516@PinkRAY:~/桌面$ cat f3
f1giaof1
f2giaof2
wh1516@PinkRAY:~/桌面$ cat f2 < f1 > f3
wh1516@PinkRAY:~/桌面$ cat f3
f2giaof2
cat - f2 < f1 > f3 2> f4
//- 从键盘输入读东西 显示f2文件内容 输入重定向到f1内容 输出重定向到f3 错误信息重定向到f4
//- 标准输入流,重定向到f1,实际执行不会输入东西,直接从f1读内容放到-处,拼上f2内容,输出到f3
//去掉-,不把f1当成参数,就没有f1输入了,直接把f2的内容写到f3内
命令组合:使用圆括号()和花括号{}将多个命令组合起来。组合命令由一个子shell执行,共享输入和输出源。组合命令能够共享同一个重定向符号。
( ls -l *.txt; wc *.txt ) > all //先做左边,再做右边
管道:将一条命令的输出作为另一条命令的输入。
格式:命令1 | 命令2( | 左边的命令使用标准输出,| 右边的命令使用标准输入)
优点:处理速度快,不产生中间文件结果。
who –a | cat > userlist
(cat f1; echo var2) | wc –l
ls | cat – file | wc //ls的输出给cat - ,接上file内容,后用wc计算
wh1516@PinkRAY:~/桌面$ who -a | cat > userlist
wh1516@PinkRAY:~/桌面$ cat userlist
系统引导 2020-10-15 14:48
wh1516 ? :0 2020-10-15 14:49 ?
1228 (:0)
运行级别 5 2020-10-15 14:49
wh1516@PinkRAY:~/桌面$ echo "1" > 1.txt
wh1516@PinkRAY:~/桌面$ cat 1.txt
1
wh1516@PinkRAY:~/桌面$ ls | cat - 1.txt | wc
15 15 109
wh1516@PinkRAY:~/桌面$ ls
1.txt cachelab-handout CH4 cond_exp.c lab2 lab4 linuxch6
c ch3 ch6emp.tar.gz datalab-handout lab3 linux OS
Shell程序变量
环境变量:shell已经定义的变量,如HOME,SHELL,PATH。
设置环境变量:export 变量名=变量值
引用环境变量:$变量名
查看环境变量:env命令
常用环境变量:
HOME 用户目录
PATH 命令执行路径,用冒号分隔,Shell会按PATH中给出的顺序搜索目录
PWD 当前工作目录的绝对路径名
UID 当前用户的识别号
RANDOM 一个随机数
设置和修改环境变量:
①在shell中直接修改,在shell中生效。
②在启动配置文件中修改,在每次登录后生效(自己用户登录时)。
系统配置文件:/etc/profile
用户配置文件:~\.profile,~/.bashrc
③设置路径变量PATH
export PATH="$PATH:newpath"
用户自定义变量:
变量名:必须以字母开头,其他可以是数字和_,区分大小写。
变量类型:shell变量都是字符串类型。
变量不需要提前声明。
变量赋值:variable=value。 //如 route=~/Documents
变量引用:$var_name(可使用转义符 \ 和单引号来避免替换)
用户自定义变量的引用和赋值:
变量引用 | 变量替换 |
$变量名 | 变量值 |
${变量名} | 变量值 |
${#变量名} | 变量值长度 |
变量赋值 | 赋值语义 |
y=${x-value} | 若x有值,则y=x,否则y=value |
y=${x=value} | 若x没有值,则y=x=value,否则y=x |
y=${x+value} | 若x有值,则y=value,否则不操作 |
y=${x?value} | 若x有值,则y=x,否则在标准错误输出value |
大括号把变量名和后面字符串区分开,value缺省,防止x值为空。
变量操作命令${var}:给定变量名,${x}返回变量的值(与$x相同)
字符串截断和提取:
操作 | 返回值 | 示例:var=~/my/file.txt |
${var#s} | 去掉从左边开始第一个匹配s的子串 | ${var#*/}=my/file.txt |
${var##s} | 去掉从左边开始最后一个匹配s的子串 | ${var##*/}=file.txt |
${var%s} | 去掉从右边开始第一个匹配s的子串 | ${var%/*}=~/my |
${var%%s} | 去掉从右边开始最后一个匹配s的子串 | ${var%%/*}=~ |
${var:m:n} | 提取从m位置开始往后的n个连续字符 | ${var:2:5}=my/fi |
字符串替换:
操作 | 返回值 | 示例:var=~/my/file.txt |
${var/s/t} | 把第一个匹配s的串替换成t | ${var/t/e}=~/my/file.ext |
${var//s/t} | 把所有的匹配s的串都替换成t | ${var//t/e}=~/my/file.exe |
运算符:数值运算。
①let命令(shell内部命令)【shell所有变量都为字符串类型,let为整型数的运算。let x=1 x为数值。不加let 作字符串处理】
②$(( 表达式 )) (shell扩展命令) 【$((括号内变量不加$符号))】
③expr命令【逻辑运算 正则表达式匹配】
④bc浮点数计算器
let x=1; echo $x
1
let x=$x+1; echo $x
2
x=1
echo $x+1
1+1
echo $((x+1))
2
bc命令:一个支持浮点数运算的计算器。通常采用管道的方式接受输入字符串并返回计算结果。
常用参数:
scale=n 保留小数点后n位(整型运算中无效)
ibase=k 指定输入为k进制
obase=k 指定输出为k进制
常用运算符:+,-,*,/,^,%
echo "scale=2;3+3*8" | bc
27
echo "scale=2;obase=2;4^2" | bc
10000
echo "scale=3; 3.8^4" | bc
208.513
数组:
数组的声明:declare -a 数组变量名
数组赋值:
①array[0]=value0
②array=(0 1 2 3)
③array=(0 [3]=1 2) //2赋值在第四个位置,前面就是NULL
④使用循环语句赋值
数组引用:
${array[i]} 访问数组array的第i个元素
${array[*]}或${array[@]} 访问数组array的所有元素
${#array[*]}或${#array[@]} 数组array的元素个数
数组销毁:
unset array 销毁数组array
unset array[i] 销毁数组array中的第i个元素
Shell程序语句
shell脚本:将一组命令保存在文件中,然后逐条执行,类似于windows的批处理文件。可以使用各种文件编辑软件来编写shell脚本。创建文件后使用chmod命令使其具有执行权限。运行时要写绝对路径,shell在PATH下搜索有无同名脚本。如在当前目录下,加./。
解释器行:#!bin/bash,在shell脚本的第一行,用来指定运行该脚本的shell种类。#!+脚本路径:/bin/sh or /bin/bash。如不写,用当前shell去解释。
命令行位置参数:运行脚本时给定的用户输入参数。可以通过特定的形式在脚本中引用这些参数。参数之间用空格隔开,类似环境变量,运行脚本程序时系统自动赋值。不能=赋值,只能引用其值。以0开头——命令行位置参数。超过9个参数时,可使用shift命令将参数移位。shift之后前面的变量就消失了,要用其他变量存下。
位置参数 | 说明 |
$0 | 所执行的命令的名字 |
$1 -- $9 | 命令行参数1到9的名字 |
$* | 所有命令行参数组合成的字符串 |
$# | 命令行参数的个数 |
$$ | 当前shell的进程ID号 |
$? | 最近一次命令的退出状态(正常0) |
$! | 最近一次后台进程的ID号 |
读取用户输入:从标准输入读取用户的输入,存入变量表中的变量。
read 变量表
-p 提示字符串
-n 指定数量读取
-t 指定时间读取
-s 隐藏显示输入字符
read –n 2 –t 10 –p "Input your name and age: " name age
命令退出状态:所有命令和程序都会返回一个退出状态值,可以通过exit命令显示指定脚本的退出状态值。(正常退出 exit 0,错误退出 exit 1)(错误时返回>0)使用exit命令时一种好的编程习惯。可以使用参数$?查看上一条命令的退出状态。
wh1516@PinkRAY:~/桌面$ ls demo;echo $?
ls: 无法访问'demo': 没有那个文件或目录
2
Shell控制结构语句:
顺序执行:在同一行内使用 ; 分隔命令
条件执行:使用逻辑运算符&&和||分隔命令
cmd1 && cmd2 若cmd1成功,则执行cmd2
cmd1 || cmd2 若cmd1失败,则执行cmd2
命令的成功与否取决于命令的退出状态值。
条件选择语句if:
格式1:
if 条件测试命令
then条件为真时的命令串
else条件为假时的命令串
fi
格式2:
if 条件测试命令
then条件为真时的命令串
elif 条件测试命令
then 条件为真时的命令串
else 条件为假时的命令串
fi
wh1516@PinkRAY:~/桌面$ cat > if.sh
#!/bin/sh
if [ -z "$*" ]
then
echo "No argument is given"
exit 1
else
echo "The argument list is $@"
fi
exit
^Z
[1]+ 已停止 cat > if.sh
wh1516@PinkRAY:~/桌面$ ls -l if.sh
-rw-r--r-- 1 wh1516 wh1516 109 10月 29 15:09 if.sh
wh1516@PinkRAY:~/桌面$ chmod 744 if.sh
wh1516@PinkRAY:~/桌面$ ls -l if.sh
-rwxr--r-- 1 wh1516 wh1516 109 10月 29 15:09 if.sh
wh1516@PinkRAY:~/桌面$ ./if.sh
No argument is given
wh1516@PinkRAY:~/桌面$ ./if.sh djf dhjf hdsj
The argument list is djf dhjf hdsj
条件测试命令:test命令(缩写[ ])功能:①比较两个数值;②比较两个字符串,或与null进行比较;③查看文件特性。test没有任何输入,只记录退出状态值$?。
数值比较运算符 | 意义 |
-eq | 等于 |
-ne | 不等于 |
-gt | 大于 |
-ge | 大于等于 |
-lt | 小于 |
-le | 小于等于 |
字符串比较运算符 | 意义 |
= | 等于 |
!= | 不等于 |
-n str | str不为空 |
-z str | str为空 |
str | str被赋值且不为空 |
x=1;y=2;z=3
test $x -eq $y;echo $?
1
[ $x -lt $z ];echo $?
0
x="str 1";y="str 2"
[ -n "$x" ];echo $?
0
[ "$x" = "$y" ];echo $?
1
文件特性检查:test命令检查文件特性。
命令 | 意义 |
[ -e file ] | file存在 |
[ -f file ] | file存在并是一个常规文件 |
[ -d file ] | file存在并是一个目录 |
[ -L file ] | file存在并是一个符号链接 |
[ -rwx file ] | file存在且可读可写可执行 |
[ -s file ] | file存在且大小大于0 |
[ f1 -nt f2 ] | f1比f2更新 |
[ f1 -ot f2 ] | f1比f2更旧 |
[ f1 -ef f2 ] | f1被链接到f2 |
复合条件:可以在if中使用复合条件运算符。
&& 和 ||
if [ "$0"="cmd" ] || [ "$0"="./cmd" ]
-a 和 -o
if [ "$0"="cmd" -o "$0"="./cmd" ]
if判断第一个参数是不是文件
[ -f $1 ]是返回0 不是返回异常
{不可读[ ! -r $1 ]
不可读文件返回exit3
可读输出文件内容}
不是文件 exit2
输出$? 观察exit状态是几
文件名带空格 error ll
[ -f error ll ]报错认为有2个参数
[ -f "error ll" ]加双引号
case语句:按照从上到下的顺序匹配表达式的值,如匹配成功则执行相应的命令串。最后一个模式匹配任何未与之前匹配的表达式。case语句也支持通配符。
case 表达式 in
模式1) 命令串1 ;;
模式2) 命令串2 ;;
…
模式 *) 命令串n
esac
//两个分号表示该分支结束 *)相当于default
循环语句for:每次迭代中列表的值被依次赋给变量,并执行循环体。当列表结束时,循环结束。
for 变量 in 列表
do
命令串
done
循环语句while:当条件测试命令返回真时,执行循环体。当条件测试命令返回假时,循环结束。
while 条件测试命令
do
命令串
done
两个字符串比较
read 两个参数存在name和number里
输出刚才的两个变量
只要answer不是y 默认为no
输出重定向到addressbook文件中
跟在echo后面的重定向 1&>2 单独重定向到标准错误内(要其显示在终端——交叉重定向or read -p)
最外层while后的重定向作用在里面的每一条echo
循环语句until:当条件测试命令返回假时,执行循环体。当条件测试命令返回真时,循环结束。
until 条件测试命令
do
命令串
done
break和continue:break语句:跳出循环体,执行done之后的语句。continue语句:跳到done的位置,重新执行循环。
shell函数:函数调用:使用函数名进行调用,并可在名称后面给出命令行参数。函数支持对命令行位置参数的引用。函数的退出状态由return返回。函数可通过标准住处向调用者返回输出字符串。
调用函数时,把要传入的参数放在调用语句后即可,函数内部参数$1,$2之类。answer数值比较,要加上数值表达式。
函数声明:
function(){
statements
return value
}