linux:shell 编程

补充

  • 脚本可以删除自己

字符:

  • 字符串相加:没有等号 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. 执行脚本

  1. 首先编写一个简单的脚本,保存为 script.sh
    #! /bin/sh
    cd ..
    ls
    
  2. 赋予可执行权限:$ chmod +x script.sh
  3. 执行:$ ./script.sh$ bash script.sh

Shell 脚本中用 #表示注释。但如果 # 位于第一行开头,并且是 #!(称为Shebang)则例外,它 表示该脚本使用后面指定的解释器 /bin/sh 解释执行

Linux 中可执行文件是靠属性指定与文件后缀没关系;不同的后缀只是为了让编译器识别(eg:语法高亮)

分析:

  1. 交互Shell(bash 终端) fork/exec 一个 子Shell(sh)用于执行脚本父进程 bash 等待子进程 sh 终止
  2. sh 读取脚本中的 cd.. 命令,调用相应的函数执行内建命令,改变当前工作目录为上一级目录。
  3. sh 读取脚本中的 ls 命令,fork/exec 这个程序,列出当前工作目录下的文件,sh 等待 ls 终止
  4. ls 终止后,sh 继续执行,读到脚本文件末尾,sh 终止
  5. sh 终止后,bash 继续执行,打印提示符等待用户输入。

2. 基本语法

2.1 变量

  • 按照惯例,Shell变量由 全大写字母加下划线组成
  1. 环境变量:环境变量 可以从父进程传给子进程,因此Shell进程的环境变量可以从当前 Shell 进程传给fork出来的子进程。用 printenv (或 env) 命令可以显示当前Shell进程的环境变量

    注:与命令行参数 argv 类似,环境变量表也是一组字符串

  2. 本地变量:只存在于当前 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进程,它是本地变量

  1. 定义变量(本地)$ VARNAME=value

    注意等号两边 都不能有空格,否则会被Shell解释成命令和命令行参数

  2. 导出为环境变量export VARNAME

  3. 也可合并以上两步:export VARNAME=value

  4. 打印一个环境变量:echo $VARNAME

    Shell变量 不需要先定义后使用,如果对一个没有定义的变量取值,则值为 空字符串

  5. 删除一个环境变量: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脚本中:单引号和双引号一样都是字符串的界定符,而不是字符的界定符。

  1. 单引号保持引号内所有字符的字面值,即使引号内的 \回车 也不例外

    注意:

    • 字符串中不能出现单引号;
    • 如果引号没有配对就输入回车,Shell会给出续行提示符,要求用户把引号配上对。
    $ echo '$SHELL'
    $SHELL
    $ echo 'ABC\(回车)
    > DE'(再按一次回车结束命令)
    ABC\
    DE
    
  2. 双引号:保持引号内 所有字符的字面值(回车也不例外)

    以下情况除外,其它字符前面的 \ 无特殊含义,只表示字面值

    • $ 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 特点

  1. 定义 foo() 函数时并不执行函数体中的命令,就像定义变量一样

  2. 调用 foo 函数的时才执行函数体中的命令 (Shell 中的 函数调用不写括号

4.2 参数 和 返回值

  1. Shell 函数没有参数列表并不表示不能传参数

  2. 函数就像是迷你脚本,调用函数时 可以传任意个参数

  3. 在函数内同样是 用 $0、$1、$2 等变量来提取参数,函数中的位置参数相当于 函数的局部变量,改变这些变量并 不会影响函数外面的$0、$1、$2等变量

  4. 函数中可以用 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

使用调试命令

  1. 在命令行提供参数

    $ sh -x ./script.sh

  2. 在脚本开头提供参数

    #! /bin/sh –x

  3. 局部代码调试

    if [ -z "$1" ]; then
    	set -x
    	echo "ERROR: Insufficient Args."
    	exit 1
    	set +x
    fi
    

    set -xset +x 分别表示 启用和禁用-x参数

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值