Shell 脚本入门学习小结

Shell 脚本基本知识小结

1. shell 脚本简介

1.1 什么是 shell

通俗一点的解释就是命令解析器,用以接收用户输入的命令,然后调用相应的应用程序.

Shell 是用户和UNIX/Linux操作系统内核程序间的一个接口.

命令
结果
用户
shell
操作系统

Shell 的种类有Bourne Shell(sh)Bourne Again Shell(bash)C Shell(csh)Korn Shell(ksh)Linux标准shellbash,也支持C ShellKorn Shell.

要注意我们敲命令的地方(也就是那个黑色框框)不叫 Shell!!!那个东西叫 terminal,我们在 terminal 中输入命令,然后 Shell 接收指令. Shell 可看成是一个进程,terminal 是这个进程的输入输出,与用户交互的部分.

详细可看知乎:终端、Shell、tty 和控制台(console)有什么区别?

1.2 什么是脚本文件

脚本文件是普通的文本文件,但该文件中是一条条的指令,操作系统在执行该脚本文件的时候,会用一种解释器(bash、Python 等)来逐行解释执行该文件中的指令. 操作系统使用什么解释器来执行该脚本文件,是需要人为来指定的(当不指定时,使用不同的 shell 会有不同的策略). 这种指定可以是这样的:

bash test.sh

表示使用 bash 解释器来解释执行 test.sh,大家运行 Python 文件的时候使用python test.py,跟这个道理是相同的. (请始终注意,文件拓展名大多数情况下都是给人看的,操作系统不会自动把.py结尾的文件看做是 Python 文件,你同样可以把一个 bash 脚本保存成test.py这样的名称,只不过这容易给人造成误解而已).

还有一种指定解释器的方法,就是在脚本文件的第一行通过注释的方式指明所使用的解释器. 就像下面这样:

#!/bin/bash

echo 'Linux is cool!'

这样,在执行该文件的时候,就不需要手动指定解释器了,只需要输入该文件的名称就可以了. 但一般这个时候执行这个文件会报错,类似于:

-bash: ./test.sh: Permission denied

咦,明明是我创建的这个文件,我再来执行它为啥会“Permission denied(没有权限)”呢?这时候查看一下这个脚本文件的权限:

-rw-rw-r-- 1 loheagn loheagn 27 Apr 28 21:33 test.sh

发现了吧?该文件确实没有可执行的权限.

这时,我们使用chmod来修改文件权限就可以了,例如可以:

chmod 764 test.sh

或者直接:

chmod +x test.sh

1.3 运行第一个 shell 脚本

helloworld.sh为例

#!/bin/bash	# 这一行称为shebang
echo "Hello, World!"
  • $bash hellowrold.sh直接运行,显示调用shell来解释脚本将忽略 shebang 的指定解释器.
  • 运行该程序需要具有可执行权限,可通过chmod +x helloworld.sh进行赋权. ./helloworld.sh即可看到输出.
  • 处于安全考虑,当前目录并没有被加在用户的$PATH变量,因此不能这样调用scripname.
  • #为注释符号.
  • #!/bin/bash被称为 shebang. 具体其来源与作用见释伴:Linux 上的 Shebang 符号(#!).
  • #!/bin/bash指定解释器,若忽略,则无法使用shell内建命令;在脚本行中则只是注释.
  • #!/bin/rm会自己删除自己;#!/bin/more打印自己.

1.4 命令连接符

命令的执行是串行的,一条命令结束才能输入下一条命令,可以在命令之间加上;分割命令,从而可以一行输入所有命令. Shell 会挨个执行.

  • &&连接符
    • 命令1 && 命令2 && 命令3 ,Shell 在判断出这个表达式的真假后就会停止执行. 如果命令 1 为false,可以判断表达式 一定为假,执行停止. 如果为true,那么还需要执行命令 2,一直执行到能判断真假为止或者执行完被&&连接的命令.
  • ||连接符
    • && , 执行到能判断真假或者所有被连接命令被执行完为止. &&||的计算方式同 c 语言中的&& ||. 以上运算原则又被称作短路原则

1.5 exit

  • exit 命令被用来结束脚本,返回状态码给shell
  • exit n,当n为 0 时,表示执行成果,非 0 表示一个错误码.
  • $?读取上一条命令的退出码
  • 脚本中若无exit语句,其返回状态将由最后一条语句的执行状态决定
  • 特定的退出吗都有预定的含义,用户不因该在自己的脚本中使用它. 如 126 表示文件不可执行,127 表示命令未找到.
#!/bin/bash

echo hello
echo $?		# 返回 0 ,因为执行成功
xxx
echo $?		# 返回非 0 ,因为失败了

exit 123	# 将返回 123 给 shell

2. shell 变量

  • shell变量不分数据类型
  • shell变量依赖上下文
  • shell允许比较操作和算术操作,关键在于变量的值中是否只有数字

2.1 环境变量

2.1.1 什么是环境变量

环境变量可以简单理解为,在程序运行所处的环境中,提前设定好的参数值. 程序在执行过程中,会去读取这些提前设定好的参数值.

举个例子,大家在 Windows 中安装完 jdk 时,双击安装完安装包后,都要去修改JAVA_HOMECLASSPATHPATH这三个环境变量. 其中当时这个JAVA_HOME我们设定的是一个目录,这样,当有软件需要使用 jdk 的时候,它就会尝试读取这个JAVA_HOME的值,从而知道系统安装的 jdk 在哪里.

Linux 默认存在的环境变量有PATHHOME等,我们可以通过echo $PATH $HOME查看. 若要查看所有的环境变量,可以直接使用$ env命令.

有没有发现对环境变量的引用与我们在 shell 脚本中引用变量的方法完全一样?因为它本质上就是已经提前定义好的变量嘛!

常用的环境变量:

环境变量名含义
HOME用于保持注册目录的完全路径名
PATHshell按照该变量的顺序搜索与名称一致的可执行文件
TERM终端类型
UID当前用户的标识
PWD当前工作目录的绝对路径
PS1主提示符,特权用户是#,普通用户是$
SHELL用户当前使用的shell. 它也指出你的shell解释程序放在什么地方.
USER当前用户名
2.1.2 设定或修改环境变量

在 Windows 中,我们一般是直接使用 GUI 界面修改,然后重启就可以了. 但在 Linux 必须使用命令行操作.

总的来说,在 Linux 中设定环境变量的语法很简单:

export environment_variable=xxxxx

但是你会发现,当你退出当前的 Terminal,再次打开一个新的 Terminal 时,将无法再次访问system_programming. 这是因为,我们上次进行的修改,是在一个 bash 的进程中修改的,当我们关闭 Terminal,就终止了这个进程;当再次启动一个新的 Terminal 时,就重新开启了一个新的进程,这个新的进程自然是访问不到别的进程的变量的. (关于进程的概念,后续学习中会有介绍).

那该如何“永久”设置环境变量呢?我们知道,当一个 bash 进程启动时(即,打开一个 Terminal 或者远程 SSH 登录时),该进程会读取~/.bashrc文件来完成初始化. 因此,我们只需要把上面提到的export语句写到~/.bashrc文件中就可以了. 注意,该文件前面加了.,也就是说这是一个隐藏文件,要使用ls -a才能看到.

当然,这种方法只能使得当前用户,也就是我们自己访问到该环境变量,对于 root 用户,或者其他注册用户来讲,是访问不到的. 为了达到所有用户都可以访问的效果,我们可以把export语句写到/etc/profile文件,当系统启动时会读取到该文件.

2.1.3 PATH 环境变量

当大家在工作目录中使用 GCC 编译出来了一个可执行文件a.out,要运行这个程序的时候是这样进行的:

./a.out

直接在 shell 中输入了要执行的文件的文件名. 而且正如上一节所看到的,执行脚本文件也是直接输入文件名. 这是在 bash shell 中执行可执行文件的唯一方式.

但如果我们不在这个可执行文件所在的目录下怎么办?如果我在一个其他目录下,想执行这个程序,就需要用相对路径或绝对路径写出整个路径前缀,这往往是一件非常麻烦的事情. 这时候,PATH就诞生了.

PATH是一个环境变量,这个环境变量指明了系统默认的查找可执行文件的路径. 你可以在 bash shell 中使用echo $PATH打印出你当前的PATH,它将如下所示:

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

这一行输出实际上是几个路径之间用:拼接起来的. 出于安全考虑 当前目录并没有被加在用户的 $PATH 变量中 . 因此 在当前目录下调用脚本 ./scriptname 这种形式.

有了PATH,当你在命令行输入一个程序名时,bash shell 就会去PATH所指定的这几个目录中去寻找该程序,如果找不到就会报错.

现在大家知道了吧,我们平常使用的指令lsmv等指令没啥神奇的,他们只不过是操作系统预设好的一个个程序,并放在了PATH指定的某个路径下,我们输入指令时,其实就相当于是在执行这些程序.

比如,我们可以用which来查看这些“内置程序”的具体路径

这一点可以去路径/usr/bin下去查看验证一下.

2.2 用户变量

  • 在 Shell 中使用变量无需定义,在使用的时候创建. 并且变量不分类型,Shell 统一认为是字符串,需要的时候通过一些命令进行转换.
  • 变量赋值: 变量名=值 ,等号左右不能够有空格. 若字符串中包含空格,则需要用单/双引号括起来.
  • 可以使用readonly将变量改为只读类型.
  • 通过 $ 引用变量值,echo $SHELL.
  • 输入变量,read变量名.
操作用法
初始化varName=varValue
只读readonly varName
普通变量调用$varName
命令变量调用$(commad) | `comand` (二者等效,推荐后者,作用是在调用的位置替换为命令结果)
  • 引号的使用
引号用法
双引号:""双引号括起来的字符 除$,\,',"都将作为普通字符对待
单引号:''均作为普通字符出现
反引号:````````命令结果替换, 等效于 $(command)

下面介绍更详细介绍几种常用的变量.

2.2.1 字符串

以下是字符串常用的操作,涉及道正则表达式的知识可以自行搜寻。

  1. 字符串长度
长度: echo `expr length $str` | ${#str}
find: echo `expr index $str $tofind` 匹配到子串的第一个字符出现的位置
匹配长度: echo `expr $str : 'abc[A-Z]*'` | `expr mathc $str 'abc[A-Z]*'`
匹配子串: echo `expr $str : '\(abc[A-Z]*\)'` | `expr mathc $str '\(abc[A-Z]*\)'`
提取子串: ${str:position:length} | echo `expr substr $str position length`
删去从第一个字符开始的子串: 第一个匹配${str#substr} | 最后一个匹配${str##substr}
删去从最后的字符结束的子串: 第一个匹配${str%substr} | 最后一个匹配${str%%substr}
子串替换:第一个 ${string/substring/replacement} | 全部 ${string//substring/replacement}
开头部分匹配${string/#substring/replacement} | 结尾部分匹配${string/#substring/replacement} 后两者不推荐
2.2.2 运算变量
算术替换(三者等效)
$((1+1))		# 推荐
$(expr $x + 1)	# expr 注意空格!
`expr $x + 1`

# 例子
i=1
((i+=1))
ecoh $i # 2

区别

调用含义
$(command) | $`command`命令替换
$((expression)) | $(expr expression) | `expr expression`算术命令替换
((command))算术运算
2.2.3 数组
  • 不要求连续,即一部分可以不被赋值,默认为NULL值
  • 无类型限制,即可以存不同类型的值
声明:declare -a array
初始化:array=( 0 1 2 ) | ( [0]=0 [1]=1 [2]=2 )
赋值:array[0]=1 array[100]=2 array[200]=abc
使用:echo ${array[0]}

例子

#!/bin/bash
area=( 1 2 3 [11]=11 [13]=13 )
area[5]=`expr ${area[11]} + ${area[13]}`
echo "area[5] = area[11] + area[13]"
echo "area[5] = "
echo ${area[5]}

#输出
#area[5] = area[11] + area[13]
#area[5] = 
#24

2.3 内部变量

2.3.1 介绍
  • 在执行 Shell 脚本的时候,可以传入参数,如当前有个脚本叫test,执行sh test arg1 arg2 arg3 ,那么在test中,$0 代表脚本文件名,​$1为第一个参数:arg1,以此类推.

  • 使用shift可以将参数左移,此时$1arg2$2arg3, $3为空$#为参数数量.

    特殊参数含义
    $#传递到脚本的参数数量
    $?前一个命令执行情况,0成功,其他值失败
    $$运行当前脚本的进程id
    $!运行脚本最后一个命令
    $@传递给脚本或者函数的所有参数。
    $0-9位置参数,分别代表:当前执行的进程名,参数1,参数2…参数8,之后用shift,参数列表左移(即$1 <-- $2, $2 <-- $3…),获取更多参数
    $*传递给脚本或者函数的所有参数.若不设置IFS,则于$@等效;若设置IFS(参数连接符,默认为空格),参数间用它连接在一起。
2.3.2 参数条件置换
用法意义
var=${param:-default}如果param已设置,则将param赋给var,否则default赋给var
var=${param:=default}如果param已设置,则将param赋给var,否则default赋给paramvar
var=${param:?default}如果param已设置,则将param赋给var,否则就显示default并从shell退出,如果省略了default,则显示标准信息,指出错误.
var=${param:+default}param已设置,则将default赋给var,否则不进行赋值而使用null字符串

3. shell 输入输出

3.1 重定向

Linux 中默认输入输出分为 3 个文件:

  • 标准输入stdin. 标准输入文件的编号是0(牢记 Linux 万物皆文件,可以用文件表示设备),默认的设备是键盘,命令执行时从键盘读取数据.
  • 标准输出stdout. 标号为1,默认的设备是显示器,命令的输出会被打印到屏幕上.
  • 标准错误stderr. 标号为2,默认的设备是显示器,命令执行产生的错误信息会被发送到标准错误文件.

重定向的意思就是改变这三个文件实际指向,比如我们希望从某个文件中获取输入,那么就需要将标准输入指向这个文件. 重定向后命令依然从标准输入获得输入,此时标准输入指向这个文件,故命令能够从这个文件获取输入.

  • 输入重定向
    格式为命令 < 文件名,比如 sort < sp.txt , 把sp.txt文件中的内容作为sort的输入.
  • 输出重定向
    格式为命令 > 文件名,比如 cat /etc/passswd > ps.logcat会输出/etc/passwd中内容,但此时并不会输出到屏幕上,而是输出到ps.log中.
  • 错误重定向
    格式为命令 2> 文件名,比如 gcc -c test.c -o test.out 2> error.log,如果gcc编译时出现错误,则会把错误信息输出到error.log中. 会覆盖原文件中内容,>> 则会将输出追加到原文件末尾.
  • 其他
    • 在重定向错误时使用了错误文件的编号2,其实在输入输出的时候也可以显式写01,通常是省略.
    • &运算符,等价于:2>&1. 表示将标准错误从重定向到标准输出指向的文件. 如 1>/dev/null ,然后执行2>&1,此时都指向空设备.

3.2 管道

管道作用是将多个命令串连起来,使一个命令的输出作为另一个命令的输入.

格式为命令1 | 命令2 | 命令3 ....| 命令n.

比如 ls /etc | grep init 将会输出 /etc 目录下,文件名包含 init 的文件/目录. 如果不使用管道,命令就得拆成: ls /etc > tmp grep init < tmp rm tmpls /etc | grep init >> test cat test.

其实管道是一种进程之间通信的手段,在之后的 Linux 系统编程的实验中我们还会经常遇到.

4. shell 控制流

4.1 条件表达式

两种形式:[ condition ] | test condition

符号含义(默认返回真的说明)
= | !=比较字符串
-n strstr长度大于0
-z strstr长度为0
strstr不为空字符
-eq | -ge | -le | -gt | -ne== | >= | <= | > | !=
-d filefile 是目录
-f filefile是普通文件
-r file | -w | -xfile是可读|可写|可执行文件
-s filefile长度大于0
!
-a | -o(条件表达式内)与 | 或
&& | ||(条件表达式间)与 | 或

4.2 常用控制流

4.2.1 if

用法

if [ condition ]
then
...
elif [ condition ]
then
...
else
...
fi

if 例子1equal.sh

#!/bin/bash
# 算术和字符串比较参数 1 和参数 2,若无对应参数,则默认值分别为 0 和 1
a=${1:-0}
b=${2:-1}

if [ $a -ne $b ]
then
        echo "$a is not equal to $b"
else
        echo "$a is equal to $b"
fi
echo "(arithmetic comparison)"

if [ $a != $b ]
then
        echo "$a is not equal to $b"
else
        echo "$a is euqal to $b"
fi
echo "(string comparison))"

if 例子2range.sh

#!/bash/bin
# 输出参数 1 的大小范围

if [ $1 -le 10 ]
then
        echo "a<=10"
elif [ $1 -le 20 ]
then
        echo '10<a<=20'
else
        echo "a>20"
fi
4.2.2 case

用法

case var in
expr1)
...	;;
1|3|5|7|9)
... ;;
*) # default
... ;;
esac

case 例子: yesorno.sh

#!/bin/bash
# 输入 y | Yes | YES 进入第一个入口,
# 输入以 n | N 开头的字符串则进入第二个入口,
# 其他输入进入第三个入口

echo "Enter yes or no?"
read answer
case $answer in
yes | y | Yes | YES) 
        echo "Good Morning!";;
[nN]*)
        echo "Good Bye!";;
*)
        echo "Sorry, invalid answer"
        exit 1;;
esac
4.2.3 while/until

用法

while condition # 假则退出
do	
command(s)
done

until condition  # 真则退出
do
command(s)
done

while 例子:guess.sh

#!/bin/bash
# 猜密码

echo 'Enter password'
read pass
while [ $pass != 'secret' ]
do
        echo "Sorry, try again"
        read pass
done
echo "Success!"
4.2.4 for

用法

for arg in [list] 
do
command(s)
done

for 例子for.sh

#!/bin/bash
# 先打印列表,然后打印当前目录下的文件

for foo in bar fud 43
do
        echo $foo
done

for file in `ls .` 
do
        echo "open file $file"
done
4.2.5 select

用法

selec var in arg1 arg2 arg3 ...
do
commands(var 为编号对应的参数)
done

select 例子: favorite.sh

#!/bin/bash

echo "输入编号选择: "
select subject in Math CS CH SP
do 
        echo "我最喜欢$subject"
        break;
done
echo

5. shell 函数

5.1 函数简介

定义
[function] fun_name (){ 	// 也可以这样 function fun_name {}
	...
	[return ...] # 若没有return,则默认返回最后一条命令执行后的状态
	[echo ...]
}
调用
fun_name arg1 arg2 ...

若使用return,则用$?接收返回值
若使用echo, 则当作表达似即可,如rlt=`fun_name arg1 ...`

5.2 函数例子

maxInThreeNums.sh

#!/bin/bash
# 该函数返回输入参数 1,参数2,参数3 中最大值

function max()
{
  	if [ $# -ne 3 ]
    then
        echo 'usage:max p1 p2 p3'
        exit 1
    fi
		
    max=$1
    if [ $max -lt $2 ]
    then
        max=$2
    fi
    if [ $max -lt $3 ]
    then
        max=$3
    fi

    return $max
#   echo $max	# 也可直接打印,无需使用下面的rlt
}

max $1 $2 $3
rlt=$?
echo "The max number of $1 $2 $3 is: $rlt"
exit
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GaspardR

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值