补充
- 脚本可以删除自己
字符:
- 字符串相加:没有等号 eg: echo “hello,”“wujie”
- $LINENO:命令行号,是内部宏定义
- $LOGNAME:存储了系统登录用户名
数字:
- 进制:012(八进制 0 开头)| 0x12(十六进制)
数组:下标从0开始
- arr=(1 2 3 4 5) # 定义数组
- echo ${arr[1]} # 访问数组元素
- arr[1]=100 # 数组元素赋值
1. 认识 shell
1. 执行方式
Shell的作用是解释执行用户的命令,用户输入一条命令,Shell就解释执行一条,这种方式称为交互式(Interactive)
Shell的另一种执行命令的方式称为批处理(Batch),用户事先写一个Shell脚本(Script),其中有很多条命令,让Shell一次把这些命令执行完,而不必一条一条地敲命令。
2. 执行交互式命令
用户在命令行输入命令后,一般情况下 Shell 会 fork 并 exec 该命令,但是Shell 的内建命令例外,执行内建命令相当于调用Shell进程中的一个函数,并不创建新的进程
内部命令 & 外部命令
- 内部命令:shell 内部函数实现
- 外部命令:外部脚本实现;执行方式:fork 一个子进程后调用 exec 去执行
判断方法:which CMDNAME
- 无返回就是内部命令,否则返回外部命令所在目录
3. 执行脚本
- 首先编写一个简单的脚本,保存为 script.sh
#! /bin/sh cd .. ls
- 赋予可执行权限:
$ chmod +x script.sh
- 执行:
$ ./script.sh
或$ bash script.sh
Shell 脚本中用 #表示注释。但如果 # 位于第一行开头,并且是 #!(称为Shebang)则例外,它 表示该脚本使用后面指定的解释器 /bin/sh 解释执行。
Linux 中可执行文件是靠属性指定与文件后缀没关系;不同的后缀只是为了让编译器识别(eg:语法高亮)
分析:
- 交互Shell(bash 终端) fork/exec 一个 子Shell(sh)用于执行脚本,父进程 bash 等待子进程 sh 终止。
- sh 读取脚本中的
cd..
命令,调用相应的函数执行内建命令,改变当前工作目录为上一级目录。 - sh 读取脚本中的
ls
命令,fork/exec 这个程序,列出当前工作目录下的文件,sh 等待 ls 终止。 - ls 终止后,sh 继续执行,读到脚本文件末尾,sh 终止。
- sh 终止后,bash 继续执行,打印提示符等待用户输入。
2. 基本语法
2.1 变量
- 按照惯例,Shell变量由 全大写字母加下划线组成
-
环境变量:环境变量 可以从父进程传给子进程,因此Shell进程的环境变量可以从当前 Shell 进程传给fork出来的子进程。用 printenv (或 env) 命令可以显示当前Shell进程的环境变量。
注:与命令行参数 argv 类似,环境变量表也是一组字符串
-
本地变量:只存在于当前 Shell 进程,用
set
命令可以显示当前Shell进程中定义的所有变量(包括本地变量和环境变量)和 函数。
区别:环境变量是任何进程都有的,而本地变量是 Shell 特有的。
程序中使用环境变量 并 打印所有环境变量:
#include<stdio.h>
extern char **environ;
int main(void)
{
int i;
for(i=0; environ[i]!=NULL; i++)
printf("%s\n", environ[i]);
return 0;
}
2.2 定义环境变量
一个变量定义后仅存在于当前Shell进程,它是本地变量
-
定义变量(本地):
$ VARNAME=value
注意等号两边 都不能有空格,否则会被Shell解释成命令和命令行参数
-
导出为环境变量:
export VARNAME
-
也可合并以上两步:
export VARNAME=value
-
打印一个环境变量:
echo $VARNAME
Shell变量 不需要先定义后使用,如果对一个没有定义的变量取值,则值为 空字符串。
-
删除一个环境变量:
unset VARNAME
【注】上述方法定义的变量时 临时的,要永久定义要写到以下文件:
/etc/*.bashrc
所用用户生效/home/lh/.bashrc
lh(当前)用户生效
2.3 使用变量
用 ${VARNAME}
可以表示它的值,在不引起歧义的情况下也可以用$VARNAME
表示它的值。
【注意】在定义变量时不用 $
,取变量值时要用$
区别与c变量:和 C 语言不同的是,Shell 变量 不需要明确定义类型,事实上 Shell 变量的值都是字符串,比如我们定义 VAR=45,其实 VAR 的值是字符串 45 而非整数。
2.4 通配符匹配
* | 匹配 0个 或 多个 任意字符 |
---|---|
? | 匹配 1个 任意字符 |
[若干字符] | 匹配方括号中任意一个字符的一次出现 |
$ ls /dev/ttyS*
$ ls ch0?.doc
$ ls ch0[012].doc // [0 1 2] : 0 1 2 出现其中 ⼀个就匹配
$ ls ch[012][0-9].doc // [0-9] : 匹配0到9范围的数字
【注】Globbing(通配符)所匹配的文件名是 由Shell展开的,也就是说 在参数还没传给程序之前已经展开了。
- 比如上述 ls ch0[012].doc 命令,如果 当前目录下有 ch00.doc 和 ch02.doc,则 传给 ls 命令的参数实际这两个文件名,而不是一个匹配字符串。
2.5 命令代换 `` 或 $()
反引号 括起来的也是一条命令:Shell 先执行该命令,然后将输出结果代换到当前变量中。
$ DATE=`date`
或者
$ DATE=$(date)
DATE: 2021年 01月 20日 星期三 16:57:45 CST
2.6 算术代换:$(())、let
-
可用运算符:
+、-、*、/、%、()、**(幂运算)、||、&&
-
只能做整数运算;且 运算结果取值将转换成整数。
-
支持
+=、-=
等
2.6.1 $(())
$ VAR=45
$ echo $(($VAR+3))
2.6.2 let
let "var *= 4"
2.7 转义字符 \
\
在 Shell 中被用作转义字符,用于去除紧跟其后的单个字符的特殊意义(回车除外);换句话说,紧跟其后的字符取字面值。
$ echo $SHELL
/bin/bash
$ echo \$SHELL
$SHELL
$ echo \\
比如:创建一个文件名为 “$ $”
的文件: $ touch \$\ \$
- 把 空格 转义成其本意(空格特殊意义 : 分割两个输入参数)
用 \
来续行
在 \
后敲回车表示续行,Shell 并不会立刻执行命令,而是把光标移到下一行,给出一个续行提示符 >
,等待用户继续输入,最后把所有的续行接到一起当作一个命令执行。
解释:回车本意: 换行;在 shell 命令行里:输入完成and提交
例如:
$ ls \
> -l (ls -l命令的输出)
2.8 单引号和双引号
Shell脚本中:单引号和双引号一样都是字符串的界定符,而不是字符的界定符。
-
单引号:保持引号内所有字符的字面值,即使引号内的
\
和回车
也不例外注意:
- 字符串中不能出现单引号;
- 如果引号没有配对就输入回车,Shell会给出续行提示符,要求用户把引号配上对。
$ echo '$SHELL' $SHELL $ echo 'ABC\(回车) > DE'(再按一次回车结束命令) ABC\ DE
-
双引号:保持引号内 所有字符的字面值(回车也不例外)
以下情况除外,其它字符前面的
\
无特殊含义,只表示字面值- $ VARNAME:可以取变量的值
- 反引号 `date`:仍表示命令替换
- \$ :表示 $ 的字面值
- \` :表示 ` 的字面值
- \” :表示 ” 的字面值
- \\ :表示 \ 的字面值
// 1. $ echo "$SHELL" /bin/bash // 2. $ echo "`date`" Sun Apr 20 11:22:06 CEST 2003 // 3\4. $ echo "I'd say: \"Go for it \$\"" I'd say: "Go for it $" // 5. $ echo "\"(回车) >"(再按一次回车结束命令) " // 6. $ echo "\\" \
3. 脚本语法
3.1 条件测试:test、[
命令test或[可以测试一个条件是否成立:
-
如果测试结果为 真,则命令的 Exit Status为 0,
-
如果测试结果为 假,则命令的 Exit Status为 1
(注意与C语言的逻辑表示正好相反)
$ VAR=2
$ test $VAR –gt 1 # great: 大于
$ echo $? # ?: 存放执行结果的临时变量
0
$ test $VAR -gt 3
$ echo $?
1
$ [ $VAR -gt 3 ] # `[ `: 测试命令; ` ]`: 不是命令,表示输入完成
$ echo $?
1
常用测试命令表
更多测试命令:
[ -d DIR ] | 如果 DIR存在并且是一个目录 则为真 |
---|---|
[ -f FILE ] | 如果 FILE存在且是一个普通文件 则为真 |
[ -z STRING ] | 如果 STRING的长度为零 则为真 |
[ -n STRING ] | 如果 STRING的长度非零 则为真 |
[ STRING1 = STRING2 ] | 如果 两个字符串相同 则为真 |
[ STRING1 != STRING2 ] | 如果两个字符串不相同 则为真 |
[ ARG1 OP ARG2 ] | ARG1/2: 整数或取值为整数 的变量;OP:比较逻辑 |
- 上面OP取值:-eq(等于)-ne(不等于)-lt(小于)-le(小于等于)-gt(大于)-ge(大于等于)
- [ equal、little、great ]
带与、或、非的测试命令:
[ ! EXPR ] | EXPR可以是 上表中的任意一种测试条件,!表示逻辑反 |
---|---|
[ EXPR1 -a EXPR2 ] | EXPR1/2可以是上表中的任意一种测试条件,-a表示逻辑与 |
[ EXPR1 -o EXPR2 ] | EXPR1/2可以是上表中的任意一种测试条件,-o表示逻辑或 |
$ [ $VAR1 -gt 20 -a $VAR2 -eq 30 ]
$ echo $?
1
if [ $int1 -ge 1 ] && [ $int2 -le 7 ]; then
echo "int1>=1 and int2<=7"
fi
if [ $int1 -ge 1 ] || [ $int2 -le 1 ]
echo "int1>=1 and int2<=7"
fi
如果上例中的 $VAR1/2 变量事先没有定义,则 被Shell展开为空字符串,会 造成测试条件的语法错误;作为一种 好的Shell编程习惯,应该 总是把变量取值放在双引号之中
$ unset VAR
$ [ -d Desktop –a $ VAR = 'abc' ] # [ -d Desktop -a = 'abc' ]
bash: [: too many arguments
$ [ -d Desktop –a "$VAR" = 'abc' ] # [ -d Desktop -a "" = 'abc' ]
$ echo $?
1
3.2 if/then/elif/else/fi
Shell脚本没有{}
括号,所以用fi
表示if
语句块的结束
3.2.1 if / then
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
三条命令:
- if [ -f ~/test.sh]
- then . ~/test.sh
- fi
$ source FileName
- 作用:在 当前 bash 环境下读取 并 执行 FileName (shell文件,非二进制文件) 中的命令。
- 注:source 命令通常用命令 “.” 来替代
注意:
- 两条命令写在同一行则需要用
;
号隔开 - 一行只写一条命令就不需要写
;
号了 - then后面有换行,但这条命令没写完,Shell 会自动续行,把下一行接在then后面当作一条命令处理。
3.2.2 if / then / else
if [ -f /bin/bash ]
then echo "/bin/bash is a file"
else echo "/bin/bash is NOT a file"
fi
3.2.3 空命令 :
if :; then echo "always true"; fi
if /bin/true; then :; fi
:
称为 空命令,该命令不做任何事,但 Exit Status 总是真。
- 此外,也可以执行
/bin/true
或/bin/false
得到真或假的 Exit Status。
3.2.4 从终端读取变量 read
echo "Is it morning? Please answer yes or no."
read YES_OR_NO # 从终端读取变量的值
if [ "$YES_OR_NO" = "yes" ]; then
echo "Good morning!"
elif [ "$YES_OR_NO" = "no" ]; then
echo "Good afternoon!"
else
echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."
exit 1
fi
exit 0
3.3 case/esac
case 命令可类比C语言的 switch/case 语句,esac 表示 case 语句块的结束。
C 语言的case只能匹配整型或字符型常量表达式,而 Shell脚本的case可以匹配 字符串 和 Wildcard(通配符),每个 匹配分支可以有若干条命令,最后一句必须以
;;
结束,执行时 找到第一个匹配的分支并执行相应的命令,然后直接跳到esac之后,不需要像C语言一样用break跳出
echo "Is it morning? Please answer yes or no."
read YES_OR_NO
case "$YES_OR_NO" in
yes|y|Yes|YES)
echo "Good Morning!";;
[nN]*)
echo "Good Afternoon!";;
*) # 默认分支必须放在最后一个 相当于default
echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."
exit 1;;
esac
exit 0
3.3.1 使用命令行参数
特殊的文件参数:
$0 | 脚本程序名称 |
---|---|
$n | 程序文件的第 n 个参数 |
$* \ $@ | 程序文件的所有参数 |
$# | 程序文件参数的个数 |
$$ | 程序文件执行时的 PID |
# 1. 命令行的 第一个、第二个参数
$1、$2...
# 2. 参数列表
$@
# 3. 使用
if [ "$1" != "" ]
then echo "$1"
fi
3.3.2 case的实用案例
例如:/etc/init.d/nfs-kernel-server
case $1 in
start)
...
stop)
...
reload)
...
restart)
...
*)
...
esac
3.4 for/do/done
- Shell脚本的for循环结构和C语言很不一样,它 类似于某些编程语言的 foreach 循环
for FRUIT in apple banana pear
do
echo "I like $FRUIT"
done
FRUIT 为循环变量,$FRUIT 的依次取值是 apple,banana,pear
3.4.1 应用
eg:要将当前目录下的 chap0、chap1、chap2 等文件名改为 chap0~ 、chap1~ 、chap2~ 等
按惯例,末尾有
~
字符的文件名表示 临时文件
这个命令可以这样写:
$ for FILENAME in chap?
do mv $FILENAME $FILENAME~
done
也可以这样写:
$ for FILENAME in `ls chap?`
do mv $FILENAME $FILENAME~
done
3.5 while/do/done
- while 的用法和 C 语言类似
直到条件满足:
echo "Please input PASS: "
read PASS
while [ "$PASS" != "123456" ];do
echo "!PASS ERROR"
echo "Pleas input again: "
read PASS
done
echo "ACCEPT!"
循环次数控制:
NUM=1
while [ "$NUM" -lt 10 ];do
echo "$NUM again..."
NUM=$(($NUM+1))
done
3.6 左移参数 shift
位置参数用shift命令左移:
- 比如
shift 3
表示原来的$4
现在变成$1
,原来的$5
现在变成$2
等等,原来的$1、$2、$3
丢弃 - 不带参数的
shift
命令 相当于shift 1
- 【
$0
不移动】
echo "The program $0 is runnig..."
echo "ArgLIst: $@"
echo "shift 3: "
shift 3
echo "ArgLIst: $@"
echo "shift : "
shift
echo "ArgLIst: $@"
4. 函数
- 和 C 语言类似,Shell 中也有函数的概念,但是 函数 定义中 没有返回值 和 参数列表
foo(){ echo "Function foo is called";}
echo "-=start=-"
foo
echo "-=end=-“
【注意】函数体的 左花括号{
和 后面的命令 之间必须有 空格或换行,如果将 最后一条命令 和 右花括号}
写在同一行,命令末尾必须有;
号。
4.1 特点
-
定义
foo()
函数时并不执行函数体中的命令,就像定义变量一样 -
调用
foo
函数的时才执行函数体中的命令 (Shell 中的 函数调用不写括号)
4.2 参数 和 返回值
-
Shell 函数没有参数列表并不表示不能传参数
-
函数就像是迷你脚本,调用函数时 可以传任意个参数
-
在函数内同样是 用 $0、$1、$2 等变量来提取参数,函数中的位置参数相当于 函数的局部变量,改变这些变量并 不会影响函数外面的$0、$1、$2等变量。
-
函数中可以用 return 命令返回,如果 return 后面跟一个数字表示函数的 Exit Status
foo(){
echo "It's a function========="
echo "1.ArgList: $@"
echo "2.Arg0: $0"
echo "3.Arg1: $1"
if [ "$1" != "" ];then
return 0 # 真
fi
return 1 # 假
}
echo "start======"
foo "$1" # 函数调用传参
echo "Function Exit status: $?" # 读取函数返回值
echo "end========"
4.3 局部变量
函数中的局部变量用 local
声明,变量的作用范围是本函数内部,函数执行完毕后局部变量被删除;
#!/bin/bash
abc=112233 #声明全局变量 abc
fun() #定义函数
{
local abc=123 #定义局部变量 abc,注意区分上面的 abc
echo "local para abc is $abc" #输出局部变量
}
5. 调试脚本
-n | 读一遍脚本中的命令但不执行,用于检查脚本中的语法错误 |
---|---|
-v | 执行脚本、并将执行过的脚本命令和执行结果打印(显示所有,详细模式) |
-x | 提供跟踪执行信息,将 执行的每一条命令和结果依次打印出来 |
1-20_script.sh:
#!/bin/bash
# Author:itxx00@gmail.com
# 第一个shell脚本
cd ..
ls
执行过程:
$ sh -v ./1-20_script.sh
#!bin/bash
# Author:itxx00@gmail.com
# 第一个shell脚本
cd ..
ls
1-13 IO_option shell teacherfile
$ sh -n ./1-20_script.sh
+ cd ..
+ ls
1-13 IO_option shell teacherfile
使用调试命令
-
在命令行提供参数
$ sh -x ./script.sh
-
在脚本开头提供参数
#! /bin/sh –x
-
局部代码调试
if [ -z "$1" ]; then set -x echo "ERROR: Insufficient Args." exit 1 set +x fi
set -x
和set +x
分别表示 启用和禁用-x
参数