【Shell编程学习】Shell基础知识
【Shell编程学习】Shell基础知识
目录
- 一、Shell简介
- 二、Shell分类
- 1、图形界面Shell(GUI Shell)
- 2、命令行界面Shell(CLI Shell)
- 三、第一个Shell脚本
- 1、编辑shell脚本
- 2、执行Shell脚本有三种方法:
- 方法1:用bash解释器执行
- 方法2:添加可执行权限
- 方法3:source命令执行,以当前默认Shell解释器执行
- 四、Shell变量
- 1、系统变量
- 2、普通变量与临时环境变量
- 1)变量定义及命名规则
- 2)普通变量与临时环境变量区别
- 3、位置变量
- 4、特殊变量
- 五、变量引用
- 1、自定义变量及引用
- 2、将命令结果作为变量值
- 六、双引号和单引号
- 七、Shell注释
- 1、单行注释
- 2、多行注释
一、Shell简介
Shell是一个C语言编写的脚本语言,它是用户与Linux的桥梁。Shell 既是一种命令语言,又是一种程序设计语言。
用户输入命令交给Shell处理,Shell将相应的操作传递给内核(Kernel),内核把处理的结果输出给用户。流程示意图:
Shell工作在Linux内核之上。
Linux是一套免费试用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。
Linux基本思想有两点:第一,一切都是文件;第二,每个软件都有确定的用途。与Unix思想十分相近。
Shell 脚本的一些优点包括:
- 自动化:Shell 脚本允许你自动化重复性任务和过程,节省时间并减少手动执行时可能出现的错误。
- 可移植性:Shell 脚本可以在各种平台和操作系统上运行,包括 Unix、Linux、macOS,甚至通过使用模拟器或虚拟机在 Windows 上运行。
- 灵活性:Shell 脚本高度可定制,可以轻松修改以满足特定需求。它们还可以与其他编程语言或实用程序结合,创建更强大的脚本。
- 易访问性:Shell 脚本易于编写,不需要任何特殊工具或软件。它们可以使用任何文本编辑器进行编辑,并且大多数操作系统都有内置的 shell 解释器。
- 集成:Shell 脚本可以与其他工具和应用程序集成,如数据库、Web 服务器和云服务,从而实现更复杂的自动化和系统管理任务。
- 调试:Shell 脚本易于调试,大多数 shell 都内置调试和错误报告工具,可以帮助快速识别和修复问题。
二、Shell分类
1、图形界面Shell(GUI Shell)
GUI为Unix或者类Unix操作系统构造一个功能完善、操作简单以及界面友好的桌面环境。主流桌面环境有KDE,Gnome等。
2、命令行界面Shell(CLI Shell)
CLI是在用户提示符下键入可执行指令的界面,用户通过键盘输入指令,完成一系列操作。
在Linux系统上主流的CLI实现是Bash,是许多Linux发行版默认的Shell。还有许多Unix上Shell,例如:
- Bourne Shell(/usr/bin/sh或/bin/sh)
- Bourne Again Shell(/bin/bash)
- C Shell(/usr/bin/csh)
- K Shell(/usr/bin/ksh)
- Shell for Root(/sbin/sh)
- ……
在一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以,像 #!/bin/sh
,它同样也可以改为 #!/bin/bash
。
#!
告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序。
三、第一个Shell脚本
1、编辑shell脚本
打开文本编辑器(可以使用 vi/vim 命令来创建文件),新建一个文件 firstshell.sh,扩展名为 sh(sh代表shell),扩展名并不影响脚本执行,见名知意就好。
[root@nieay linux]# vim firstshell.sh
[root@nieay linux]# cat firstshell.sh
#!/bin/bash
echo "Hello World!"
#!
是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell。
echo 命令用于向窗口输出文本。
2、执行Shell脚本有三种方法:
方法1:用bash解释器执行
[root@nieay linux]# bash firstshell.sh
Hello World!
当前终端会新生成一个子bash去执行脚本。
或
[root@nieay linux]# /bin/sh firstshell.sh
Hello World!
这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用。
方法2:添加可执行权限
[root@nieay linux]# ll
-rw-r--r--. 1 root root 34 10月 10 10:09 firstshell.sh
[root@nieay linux]# chmod +x ./firstshell.sh #使脚本具有执行权限
[root@nieay linux]# ll
-rwxr-xr-x. 1 root root 34 10月 10 10:09 firstshell.sh
[root@nieay linux]# ./firstshell.sh #执行脚本
Hello World!
注意:一定要写成 ./test.sh,而不是 test.sh,运行其它二进制的程序也一样,直接写 test.sh,linux 系统会去 PATH 里寻找有没有叫 test.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 firstshell.sh 是会找不到命令的,要用 ./firstshell.sh 告诉系统说,就在当前目录找。
方法3:source命令执行,以当前默认Shell解释器执行
[root@nieay linux]# source firstshell.sh
Hello World!
四、Shell变量
1、系统变量
在命令行提示符直接执行env、set查看系统或环境变量。env显示用户环境变量,set显示Shell预先定义好的变量以及用户变量。可以通过export导出成用户变量。
写Shell脚本时常用的系统变量:
变量 | 注释 |
---|---|
$SHELL | 默认Shell |
$HOME | 当前用户家目录 |
$IFS | 内部字段分隔符 |
$LANG | 默认语言 |
$PATH | 默认可执行程序路径 |
$PWD | 当前目录 |
$UID | 当前用户ID |
$USER | 当前用户 |
$HISTSIZE | 历史命令大小,可通过HISTTIMEFORMAT变量设置命令执行时间 |
$RANDOM | 随机生成一个0至32767的整数 |
$HOSTNAME | 主机名 |
2、普通变量与临时环境变量
1)变量定义及命名规则
普通变量定义:VAR=value
临时环境变量定义:export VAR=value
变量引用:$VAR
定义变量时,变量名不加美元符号($)。
变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样。
变量名的命名须遵循如下规则:
- 只包含字母、数字和下划线: 变量名可以包含字母(大小写敏感)、数字和下划线 _,不能包含其他特殊字符。
- 不能以数字开头: 变量名不能以数字开头,但可以包含数字。
- 避免使用 Shell 关键字: 不要使用Shell的关键字(例如 if、then、else、fi、for、while 等)作为变量名,以免引起混淆。
- 使用大写字母表示常量: 习惯上,常量的变量名通常使用大写字母,例如 PI=3.14。
- 避免使用特殊符号: 尽量避免在变量名中使用特殊符号,因为它们可能与 Shell 的语法产生冲突。
- 避免使用空格: 变量名中不应该包含空格,因为空格通常用于分隔命令和参数。
有效的 Shell 变量名示例如下:
VAR="baidu.com"
var="abc"
LD_PATH="/bin/"
_var="123"
var2="abc"
无效的变量命名:
# 避免使用if作为变量名
if="my_var"
# 避免使用 $ 等特殊符号
var_$=42
?var=123
my*var=run
my-var=123
# 避免空格
my var="abc"
# 避免以数字开头
2var="123"
等号两侧避免使用空格:
# 正确的赋值
my_var=abc
# 有可能会导致错误
my_var = abc
2)普通变量与临时环境变量区别
Shell进程的环境变量作用域是Shell进程,当export导入到系统变量时,则作用域是Shell进程及其Shell子进程。
[root@nieay ~]# ps axjf |grep pts
120425 120439 120425 120425 ? -1 S 1000 0:00 | \_ sshd: eliah@pts/0
120439 120443 120443 120443 pts/0 120670 Ss 1000 0:00 | \_ -bash
120443 120614 120614 120443 pts/0 120670 S 0 0:00 | \_ su
120614 120622 120622 120443 pts/0 120670 S 0 0:00 | \_ bash
120622 120670 120670 120443 pts/0 120670 R+ 0 0:00 | \_ ps axjf
120622 120671 120670 120443 pts/0 120670 S+ 0 0:00 | \_ grep --color=auto pts
[root@nieay ~]# echo $$
120622
[root@nieay ~]# VAR=123
[root@nieay ~]# echo $VAR
123
[root@nieay ~]# bash
[root@nieay ~]# echo $$
120680
[root@nieay ~]# ps axjf |grep pts
120425 120439 120425 120425 ? -1 S 1000 0:00 | \_ sshd: eliah@pts/0
120439 120443 120443 120443 pts/0 120712 Ss 1000 0:00 | \_ -bash
120443 120614 120614 120443 pts/0 120712 S 0 0:00 | \_ su
120614 120622 120622 120443 pts/0 120712 S 0 0:00 | \_ bash
120622 120680 120680 120443 pts/0 120712 S 0 0:00 | \_ bash
120680 120712 120712 120443 pts/0 120712 R+ 0 0:00 | \_ ps axjf
120680 120713 120712 120443 pts/0 120712 S+ 0 0:00 | \_ grep --color=auto pts
[root@nieay ~]# echo $VAR[root@nieay ~]# exit
exit
[root@nieay ~]# echo $VAR
123
[root@nieay ~]# export VAR
[root@nieay ~]# bash
[root@nieay ~]# echo $$
120722
[root@nieay ~]# ps axjf |grep pts
120425 120439 120425 120425 ? -1 S 1000 0:00 | \_ sshd: eliah@pts/0
120439 120443 120443 120443 pts/0 120763 Ss 1000 0:00 | \_ -bash
120443 120614 120614 120443 pts/0 120763 S 0 0:00 | \_ su
120614 120622 120622 120443 pts/0 120763 S 0 0:00 | \_ bash
120622 120722 120722 120443 pts/0 120763 S 0 0:00 | \_ bash
120722 120763 120763 120443 pts/0 120763 R+ 0 0:00 | \_ ps axjf
120722 120764 120763 120443 pts/0 120763 S+ 0 0:00 | \_ grep --color=auto pts
[root@nieay ~]# echo $VAR
123
[root@nieay ~]# ps -ef |grep ssh
root 1186 1 0 10月09 ? 00:00:00 /usr/sbin/sshd -D -
ps axjf
输出的第一列是PPID(父进程ID),第二列是PID(子进程ID)
当SSH连接Shell时,当前终端PPID(-bash)是sshd守护程序的PID(root@pts/0),因此在当前终端下的所有进程的PPID都是-bash的PID,比如执行命令、运行脚本。
所以当在-bash下设置的变量,只在-bash进程下有效,而-bash下的子进程bash是无效的,当export后才有效。
进一步说明:再重新连接SSH,去除上面定义的变量测试下
[root@nieay linux]# ps -axjf |grep pts2518 2574 2518 2518 ? -1 S 1000 0:09 | \_ sshd: eliah@pts/02574 2579 2579 2579 pts/0 28018 Ss 1000 0:00 | \_ -bash2579 2704 2704 2579 pts/0 28018 S 0 0:00 | | \_ sudo su2704 2787 2704 2579 pts/0 28018 S 0 0:00 | | \_ su2787 2790 2790 2579 pts/0 28018 S 0 0:00 | | \_ bash2790 28018 28018 2579 pts/0 28018 R+ 0 0:00 | | \_ ps -axjf2790 28019 28018 2579 pts/0 28018 S+ 0 0:00 | | \_ grep --color=auto pts
[root@nieay linux]# echo $$
2790
[root@nieay linux]# VAR=123
[root@nieay linux]# vim test.sh
[root@nieay linux]# cat test.sh
#!/usr/bin
ps -axjf |grep pts
echo $$
echo $VAR
[root@nieay linux]# bash test.sh2518 2574 2518 2518 ? -1 S 1000 0:09 | \_ sshd: eliah@pts/02574 2579 2579 2579 pts/0 28901 Ss 1000 0:00 | \_ -bash2579 2704 2704 2579 pts/0 28901 S 0 0:00 | | \_ sudo su2704 2787 2704 2579 pts/0 28901 S 0 0:00 | | \_ su2787 2790 2790 2579 pts/0 28901 S 0 0:00 | | \_ bash2790 28901 28901 2579 pts/0 28901 S+ 0 0:00 | | \_ bash test.sh28901 28902 28901 2579 pts/0 28901 R+ 0 0:00 | | \_ ps -axjf28901 28903 28901 2579 pts/0 28901 S+ 0 0:00 | | \_ grep pts
28901[root@nieay linux]# export VAR
[root@nieay linux]# bash test.sh2518 2574 2518 2518 ? -1 S 1000 0:09 | \_ sshd: eliah@pts/02574 2579 2579 2579 pts/0 29270 Ss 1000 0:00 | \_ -bash2579 2704 2704 2579 pts/0 29270 S 0 0:00 | | \_ sudo su2704 2787 2704 2579 pts/0 29270 S 0 0:00 | | \_ su2787 2790 2790 2579 pts/0 29270 S 0 0:00 | | \_ bash2790 29270 29270 2579 pts/0 29270 S+ 0 0:00 | | \_ bash test.sh29270 29271 29270 2579 pts/0 29270 R+ 0 0:00 | | \_ ps -axjf29270 29272 29270 2579 pts/0 29270 S+ 0 0:00 | | \_ grep pts
29270
123
所以在当前shell定义的变量一定要export,否则在写脚本时,会引用不到。
还需要注意的是退出终端后,所有用户定义的变量都会清除。
在/etc/profile下定义的变量就是这个原理,后面有章节会讲解Linux常用变量文件。
3、位置变量
位置变量指的是函数或脚本后跟的第n个参数。
$1-$n,需要注意的是从第10个开始要用花括号调用,例如${10}。
变量 | 描述 |
---|---|
$0 | 当前脚本的文件名 |
$1~$9 | 第 1 到第 9 个参数 |
${n} | 第 n 个参数(n ≥ 10,如 ${10}) |
shift
可对位置变量控制,例如:
[root@nieay linux]# cat test1.sh
#!/usr/bash
echo "文件名:$0"
echo "第一个参数:$1"
shift
echo "第二个参数:$2"
shift
echo "第三个参数:$3"
echo "第五个参数:${5}"
[root@nieay linux]# bash test1.sh a b c d e f g
文件名:test1.sh
第一个参数:a
第二个参数:c
第三个参数:e
第五个参数:g
每执行一次shift
命令,位置变量个数就会减一,而变量值则提前一位。shift n
,可设置向前移动n位。
4、特殊变量
变量 | 描述 |
---|---|
$0 | 脚本自身名字 |
$? | 返回上一条命令是否执行成功,0为执行成功,非0则为执行失败 |
$# | 位置参数总数 |
$* | 所有的位置参数被看做一个字符串,如"$*“用「”」括起来的情况、以"$1 $2 … $n"的形式输出所有参数。 |
$@ | 每个位置参数被看做独立的字符串,如"$@“用「”」括起来的情况、以"$1" “2"…"2" … "2"…"n” 的形式输出所有参数。 |
$$ | 当前进程PID |
$! | 上一条运行后台进程的PID |
$- | 显示Shell使用的当前选项,与set 命令功能相同。 |
$*
与 $@
区别:
- 相同点:都是引用所有参数。
- 不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,则 " * " 等价于 “1 2 3”(传递了一个参数),而 “@” 等价于 “1” “2” “3”(传递了三个参数)。
示例:
[root@nieay linux]# vim test2.sh
[root@nieay linux]# cat test2.sh
#!/bin/bash
echo "执行的文件名:$0"
echo "第一个参数为:$1"
echo "参数个数:$#"
echo "当前进程ID号:$$"
echo "上一个进程ID号:$!"
echo "所有参数 (独立): $@"
echo "所有参数 (合并): $*"for i in "$*"; doecho "\$*参数:$i"
donefor i in "$@"; doecho "\$@参数:$i"
done
echo "退出状态:$?"
[root@nieay linux]# bash test2.sh aaa bbb ccc "ab c"
执行的文件名:test2.sh
第一个参数为:aaa
参数个数:4
当前进程ID号:53331
上一个进程ID号:
所有参数 (独立): aaa bbb ccc ab c
所有参数 (合并): aaa bbb ccc ab c
$*参数:aaa bbb ccc ab c
$@参数:aaa
$@参数:bbb
$@参数:ccc
$@参数:ab c
退出状态:0
五、变量引用
赋值运算符 | 示例 |
---|---|
= | 变量赋值 |
+= | 两个变量相加 |
1、自定义变量及引用
[root@nieay linux]# a=123
[root@nieay linux]# echo $a
123
[root@nieay linux]# a+=123
[root@nieay linux]# echo $a
123123
Shell中所有变量引用使用$
符,后跟变量名。
有时个别特殊字符会影响正常引用,那么需要使用${VAR}
,如:
[root@nieay linux]# a=123
[root@nieay linux]# echo $a_[root@nieay linux]# echo ${a}
123
Shell允许a_为变量名,所以此引用认为这是一个有效的变量名,故此返回空。
有时变量名与其他字符串紧碍着,也会误认为是整个变量:
[root@nieay linux]# echo $a123[root@nieay linux]# echo ${a}123
123123
2、将命令结果作为变量值
[root@nieay linux]# a='echo 123'
[root@nieay linux]# echo $a
echo 123
[root@nieay linux]# a=`echo 123`
[root@nieay linux]# echo $a
123
[root@nieay linux]# a=$(echo 123)
[root@nieay linux]# echo $a
123
这里的反撇号等效于$()
,都是用于执行Shell命令。
注意:反撇号`,不是单引号‘
六、双引号和单引号
在变量赋值时,如果值有空格,Shell会把空格后面的字符串解释为命令:
[root@nieay linux]# a=1 2 3
bash: 2: 未找到命令...
[root@nieay linux]# a="1 2 3"
[root@nieay linux]# echo $a
1 2 3
[root@nieay linux]# a='1 2 3'
[root@nieay linux]# echo $a
1 2 3
这个看不出区别,比如:
[root@nieay linux]# b=3
[root@nieay linux]# a="1 2 $b"
[root@nieay linux]# echo $a
1 2 3
[root@nieay linux]# a='1 2 $b'
[root@nieay linux]# echo $a
1 2 $b
单引号是告诉Shell忽略特殊字符,而双引号则解释特殊符号原有的意义,比如$
、!
。
单引号字符串的限制:
- 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;
- 单引号字符串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。
双引号的优点:
- 双引号里可以有变量
- 双引号里可以出现转义字符
七、Shell注释
1、单行注释
以 #
开头的行就是注释,会被解释器忽略。
通过每一行加一个 # 号设置多行注释,如:
#--------------------------------------------
# 这是一个注释#--------------------------------------------
##### 用户配置区 开始 #####
#
#
# 这里可以添加脚本描述信息
#
#
##### 用户配置区 结束 #####
如果在开发过程中,遇到大段的代码需要临时注释起来,过一会儿又取消注释,怎么办呢?
每一行加个#
符号太费力了,可以把这一段要注释的代码用一对花括号括起来,定义成一个函数,没有地方调用这个函数,这块代码就不会执行,达到了和注释一样的效果。
2、多行注释
使用 Here 文档
多行注释还可以使用以下格式:
:<<EOF
注释内容...
注释内容...
注释内容...
EOF
:
是一个空命令,用于执行后面的 Here 文档,<<'EOF'
表示开启 Here 文档,COMMENT
是 Here 文档的标识符,在这两个标识符之间的内容都会被视为注释,不会被执行。
EOF 也可以使用其他符号:
: <<'COMMENT'
这是注释的部分。
可以有多行内容。
COMMENT:<<'
注释内容...
注释内容...
注释内容...
':<<!
注释内容...
注释内容...
注释内容...
!
直接使用 :
号
可以使用冒号 :
命令,并用单引号 '
将多行内容括起来。由于冒号是一个空命令,这些内容不会被执行。
格式为:: + 空格 + 单引号
。
: '
这是注释的部分。
可以有多行内容。
'