携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第10天,点击查看活动详情
什么是shell
1、什么是shell?“shell”的英语意思是:蛋、坚果、某些种子和某些动物的壳。注意:是xxx的壳,那么在Linux中,它就可以理解为是Linux操作系统的“外壳”。它工作于Linux内核与用户之间的解释程序(解释器)。
shell位于内核于用户之间,那么它更形象于是Linux内核和用户之间的“翻译官”。用户(比如:坐在电脑面前的你)想要操控Linux系统的虚拟机或者服务器,就需要用户输入请求(这个请求语言是人类的语言,电脑肯定不会懂)然后经由shell解释器去“翻译”成虚拟机可以理解的程序,最后交由内核去完成发来的请求
现在说Linux的shell也许还有些陌生,其实windows也有shell解释器:
C:\Windows\System32\cmd.exe
在Windows下快捷键:win+R,就是这个东西:
2、shell是一种脚本语言
脚本:本质是一个文件,文件里面存放的是特定格式的命令,在Linux中其实就是一条一条的Linux命令。系统可以使用脚本解释器(比如:shell解释器)进行翻译或解析指令并执行(它不需要编译)
shell命令解析器: 系统提供 shell命令解析器: sh、bash(这个是默认的) 查看自己linux系统的默认解析:
[root@node3 ~]# echo $SHELL
/bin/bash
可以看到shell解释器就是“bash”这个文件所提供的。bash是默认的Linuxshell解释器
登录shell
登录shell是用户登录后的第一个程序
及最常见的“Linux命令行”环境
以交互方式运行,用户每输入一个命令行,立即解释并执行
如下:
Last login: Thu Jan 20 03:29:09 2022 from 192.168.9.1 #这个是用户登录
[root@node3 ~]# ls #这个就是用户登陆后第一个命令,可以看到输出“ls”命令,shell解释器直接解释并执行,之后返回结果
anaconda-ks.cfg
上面说到是“以交互”运行,其实shell脚本命令的工作方式由两种:
- 交互式(Interactive):用户每输入一条命令就执行
- 批处理(Batch):由用户实现编好一个完整的shell脚本,这个shell脚本有许多命令组成,shell会一次行执行脚本中诸多的命令
交互式 vs 非交互式(批处理)
-
交互式
人工干预、智能化程度高
逐条解释执行、效率低
-
非交互式
需要提前设计、智能化难度大
批量执行、效率高
方便在后台静悄悄地运行
其中批处理(非交互式)就是下面要说的shell脚本
手动选择Shell环境
如果不想使用默认的bash 解释器,我们可以选择使用其它的比如:
常见的Shell:bash、zsh、tsch
如果使用zsh、tcsh需要安装
[root@node3 ~]# yum install -y zsh
[root@node3 ~]# yum install -y tcsh
#查看/etc/shells,会发现多个来zsh、tcsh文件
[root@node3 ~]# cat /etc/shells
/bin/sh
/bin/bash
/sbin/nologin
/usr/bin/sh
/usr/bin/bash
/usr/sbin/nologin
/bin/zsh
/bin/tcsh
/bin/csh
之后想用zsh,tcsh只需要直接输入改命令即可
[root@node3 ~]# zsh
[root@node3]~# tcsh
当我们在新开一个shell环境时,就像当于新建一个shell子环境(父--->子进程)
第一个Shell脚本程序
脚本的创建过程
如何写出自己的第一个Shell脚本?
1、整理任务---自然语言(人类可懂的语言)进行步骤拆分、顺序化整理
2、编写可执行语句---脚本语言:实现各步骤
3、完善脚本---如何使得界面友好,整洁,结构规范,代码的优化
4、赋予脚本可执行权限
5、运行脚本
比如说:现在有个需求:
新建一个名为zheng的本地用户,此用户能正常登录,并且设置其密码为:123456
我先写,先感受一遍里面的内容下面讲到
[root@node3 ~]# pwd
/root
[root@node3 ~]# vi first.sh
#!/bin/bash
#第一步:新建用户
useradd zheng
#设置用户密码
passwd zheng
[root@node3 ~]# chmod +x first.sh #赋予其脚本可执行权限
[root@node3 ~]# ./first.sh #运行其脚本
Changing password for user zheng.
New password: #输入密码
BAD PASSWORD: The password is shorter than 8 characters
Retype new password: #再次输出密码
passwd: all authentication tokens updated successfully.
#查看用户
[root@node3 ~]# cat /etc/passwd |grep zheng
zheng:x:1000:1000::/home/zheng:/bin/bash
可以看到脚本成功运行。
脚本语法初识
以上面的脚本为例
可以看到脚本名字为:first.sh,后缀为.sh,首先脚本名字可以随意定义,建议加上.sh后缀,以表示是个脚本文件
#!/bin/bash #声明使用的那种shell环境,此处是bash,它表明此行代码下的所有内容由shell解释器开始解释运行
#第一步:新建用户 #可以看到“#”,“#”代表注释,表明此行没有实际意义,便于读者理解代码
useradd zheng #脚本内容
#设置用户密码
passwd zheng
“chmod +x first.sh”,代表赋予此脚本可执行的权限
“./first.sh”,执行脚本
在执行脚本时,有三种执行方式,不同的写法对应的含义也不一样,如下:
三种执行脚本的方式
方法一:指定特定的Shell解释器来解释程序
[root@node3 ~]# bash first.sh 或者
[root@node3 ~]# sh first.sh
此方法不用执行:“chmod +x first.sh”,也就是不用给刚写好的脚本赋予可执行文件
直接指明使用bash或sh解释器来执解析并执行脚本即可
方法二:
在脚本写好后执行:“chmod +x first.sh”,将脚本作为独立运行的脚本程序
之后:
[root@node3 ~]# ./first.sh
[root@node3 ~]# /root/first.sh
[root@node3 ~]# . first.sh
这三种都行,其中前两个是:
以脚本种#!指定的shell解释器进行解析,如果#!指定的不存在,那会使用系统默认的解释器
第三个是:
直接使用默认的解释器,不会按照#!指定的解释器,但是第一行的”#!xxx“,还是需要写的
那所以当我们使用:”. first.sh或bash first.sh “,就会创建个子shell解析脚本
调试shell脚本
在写完脚本后难免会出错,那出错我们就要排错,所以就要调试的去排错
进行脚本调试也简单
第一个就是根据运行脚本时出错的提示进行调试
第二个运行命令时加个参数即可
[root@node3 ~]# bash -x first.sh
+ useradd zheng #语句一
useradd: user 'zheng' already exists #语句一输出
+ passwd zheng #语句二
Changing password for user zheng. #语句二输出
New password:
有的时候在写完脚本,为避免出大错的情况下,我们可以进行预先执行脚本
即使先执行以便脚本呢,但这次不会真正的产生效果,而是为了检查代码是否有错误
也简单,加个”-n“参数即可
[root@node3 ~]# bash -n first.sh
免交互及输出处理
可以看到上面的first.sh脚本,密码任然需要用户手动输入,并且有些输出对我们无关紧要,我们也可以选择不让它显示出来,那该怎么做呢?如下:
免交互处理
在Linux命令行中有很多需要交互的操作,比如:
- passwd改密码
- ssh远程登录
- vi文本编辑
- 图形化的安装/配置过程...
first.sh脚本为例
first.sh脚本的不太友好的原因:
- 需要用户交互输入密码
- 输出不必要的信息
- 错误日志输出到屏幕上,我想让其输出到某个文件中
以下是改进的脚本文件
[root@node3 ~]# vi first.sh
#!/bin/bash
#第一步:新建用户
useradd zheng 2> /tmp/err.log #”2>“将错误输出,写入到/tmp/err.log 文件中
#设置用户密码
echo "123456" | passwd --stdin zheng &>/dev/null #实现自动化的参数”--stdin“即可,”&>“将标准输出到null设备文件,/dev/null设备文件,是个单向文件,只能在里面写入内容,无法从里面读出内容,俗称”黑洞“
此时查看/tmp/err.log文件的内容
[root@node3 ~]# cat /tmp/err.log
useradd: user 'zheng' already exists #可以看到错误输出,输入到这个文件按里去了
这样脚本文件就很友好
命令组合运用
我们在Linux命令行输入命令时,基本上都是输入个命令,然后回车执行,然后再输,在回车执行...
这样如果前一个命令运行时间很长,那我们想要运行下一个命令就需要一直等到上一个命令运行结束,那中间的时间我们需要一直坐在电脑旁看着这样就浪费时间。比如:在源码包编译时,make 和make install两条命令。再者我们想要前一个命令和后一个命令有所关联,比如:只有前面的命令成功我才执行后面的命令,负责不执行...说白了判断和查询一定要敲两次命令吗?不一定,那下面就解决
顺序分隔
使用分号:
- 命令1 ; 命令2 ; 命令三 ...
- 依次执行,只有先后,没有逻辑关系
[root@node3 ~]# mkdir bo ; cd bo
[root@node3 bo]# pwd
/root/bo
逻辑”与“分隔
使用&&符号:
- 命令1 && 命令2 && 命令3 &&
- 逻辑关系为”而且(and)”,意思就是只有前面的命令执行成功,才会执行后一个命令,比如:命令1执行成功才会执行命令2...
- 一旦前面的命令执行失败,后面的命令不在执行
效果:
[root@node3 ~]# echo "1" && echo "2"
1
2
#典型应用
[root@node3 ~]# make && make install
逻辑“或”分隔
使用||符号
- mingling1 || 命令2 || 命令3...
- 逻辑关系为“或者(or)”,任何一条命令执行成功,就不再执行其它的命令
- 只有前面的命令执行失败才会执行后面的命令
效果:
[root@node3 ~]# echo "1" || echo "2"
1
[root@node3 ~]# id bo || useradd bo #当没有bo用户,创建bo用户
来个简单的判断操作:
组合逻辑分隔
- 命令1 && 命令2 || 命令3
- 当命令1执行成功,会执行命令2
- 当命令1执行失败,会执行命令3
[root@node3 ~]# id ff &>/dev/null && echo "yes" || echo "no" #判断ff用户是否存在
no
[root@node3 ~]# id bo &>/dev/null && echo "yes" || echo "no" #判断bo用户是否存在
yes
#判断cpu是否支持虚拟化
[root@node3 ~]# grep -q -e 'vmx|svm' /proc/cpuinfo && echo "yes" || echo "no"
no #-e用来支持后面'vmx|svm'这样的表达式
管道操作
管道的作用:
将命令的屏幕输出交给另一端的命令来处理
- 格式:命令1 | 命令2 | 命令3
- 后续命令要能正确处理传来的文本,否则无意义
案例:计算/etc/目录下有多少个文件
[root@node3 ~]# find /etc/ -type f |wc -l
1676
标准输入输出
在使用计算机的过程中,用户怎样把需要处理的信息输入给操作系统?操作系统又怎样把处理之后的结果输出给用户?这个是实现“人机合一”的关键所在。
我们都知道在Linux中一切皆文件,包括:鼠标,键盘,光盘...
提到人和计算机交互,那就要提到I/O交互设备,在Linux中主要三种交互情况
- 标准输入:从此设备中接收用户输入的数据
- 标准输出:通过此设备向用户报告正常的命令输出结果
- 标准错误输出:通过此设备向用户报告错误的命令输出结果
| 类型 | 设备文件 | 文件描述号 | 默认设备 |
|---|---|---|---|
| 标准输入 | /dev/stdin | 0 | 键盘 |
| 标准输出 | /dev/stdout | 1 | 显示器 |
| 标准错误输出 | /dev/stderr | 2 | 显示器 |
关于命令的执行结果:
- 运行成功,其屏幕信息报告给stdout设备
- 运行失败,其屏幕信息报告给stderr设备
[root@node3 ~]# ll /rootx/ /root/
ls: cannot access /rootx/: No such file or directory #标准错误输出
/root/:
total 8
-rw-------. 1 root root 1260 Sep 17 11:28 anaconda-ks.cfg #标准输出
drwxr-xr-x. 2 root root 6 Jan 20 08:30 bo
-rwxr-xr-x. 1 root root 139 Jan 20 08:15 first.sh
重定向操作
在执行Linux命令行的过程中需要输入的信息,会来自用户的键盘操作,而命令执行的结果也会通过显示器输出给用户。但在Shell脚本中这个交互的方式是不方便的,因此我们需要非交互式的输入输出技能操作,这就是重定向。
什么是重定向?
就是重新指定命令执行时I/O设备的方向
不在使用默认的键盘,显示器进行输入输出
改用指定的文本文件,需要输入操作时,用文件来存储内容,需要输出操作时,读取文件即可
重定向的类型
根据I/O方向和类别区分
| 类型 | 操作符 | 用途 |
|---|---|---|
| 重定向输入 | < | 将文本输入来源由键盘改为指定的文件 |
| 重定向输出 | 将命令行的正常执行输出保存到文件,而不是直接显示在屏幕上 | |
| 重定向输出 | >> | 与>类似,但操作是追加而不是覆盖 |
| 重定向错误输出 | 2> | 将命令行执行错误的输出保存到文件,而不是直接显示在屏幕上 |
| 重定向错误输出 | 2>> | 与2>类似,但操作是追加而不是覆盖 |
| 混合重定向 | &> | 相当于>和2>,覆盖到同一个文件 |
混合重定向案例
[root@node3 ~]# ls /rootx/ /root/ > gg 2> err.log
[root@node3 ~]# cat gg
/root/:
anaconda-ks.cfg
bo
err.log
first.sh
gg
hh
[root@node3 ~]# cat err.log
ls: cannot access /rootx/: No such file or directory
[root@node3 ~]# ls /rootx/ /root/ &> gg
[root@node3 ~]# cat gg #可以看到标准输出和标准错误错误输出在同一个文件
ls: cannot access /rootx/: No such file or directory
/root/:
anaconda-ks.cfg
bo
err.log
first.sh
gg
hh
#上面的"ls /rootx/ /root/ &> gg"写法相当于:"ls /rootx/ /root/ > gg 2>&1"
Shell变量
1、什么是变量?
变量就是以固定名称存放的可能会变化的值
变量的作用:
- 提高脚本对任务需求、运行环境变化的适应能力
- 在脚本执行中方便重复使用某个值
2、变量的格式:
- 变量名=变量值,比如:
X=12 或者 X=13 或者 X=16
定义变量的相关注意事项:
- 若指定的变量名已存在,相当于为此变量重新赋值
- 等号两边不要有空格
- 变量名由字母/数字/下划线组成,区分大小写
- 变量名不能以数字开头,不要使用关键字和特殊字符
3、查看/引用变量
基本格式:
- 引用变量值:$变量名
- 查看变量值:echo {变量名}
[root@node3 ~]# echo $x
12
[root@node3 ~]# echo $x1 #可以看到这个没有输出,我们就像在x变量后面加个“1”,那个时候shell会当成引用的是x1这个变量名,所以我们在这种情况下,需要加上{}
[root@node3 ~]# echo ${x}1 #变量名易混淆时,以{}为界定
121
4、取消变量
自定义变量的失效:
- 退出定义变量的shell环境时,变量会自动失效
- 在shell环境内也可手动取消:unset 变量名......
[root@node3 ~]# x=16
[root@node3 ~]# echo $x
16
[root@node3 ~]# unset x
[root@node3 ~]# echo $x #输出为空
5、变量的分类
-
存储类型
在其它的编程语言中,比如:c、c++、Java,变量的存储类型有:整数型,浮点型,双精度浮点型、字符串...
Shell不作为高级编程语言,对存储类型的要求比较松散
-
变量的使用类型
类型 说明 环境变量 变量名通常都大写,由系统维护,用来设置工作环境,其中只有个别变量用户可以直接更改,比如:$SHELL 位置变量 由Bash内置,用来存储在执行脚本时提供的命令行参数 预定义变量 由Bash内置,一类由特殊用途的变量,可以直接调用,但不能直接赋值或修改 自定义变量 由用户自主设置、修改及使用
环境变量:
-
设置环境变量的配置文件:
/etc/profile #这个文件定义的环境变量,针对Linux系统中所有的用户都起作用,当用户第一次登录时,该文件被执行,系统的公共环境变量在此设置,开始自启动的程序,一般也在这里设置 ~/.bash_profile #这个文件位于某个用户的家目录下,只针对当前用户的设置,登录时会自动调用,打开任意终端时也会自动调用 -
列出环境变量:env、set命令
[root@node3 ~]# env #env命令列出所有的环境变量 XDG_SESSION_ID=17 HOSTNAME=node3 SELINUX_ROLE_REQUESTED= TERM=vt100 SHELL=/bin/bash HISTSIZE=1000 SSH_CLIENT=192.168.9.1 53062 22 ... [root@node3 ~]# set #set命令列出所有的变量 BASH=/bin/bash BASHOPTS=checkwinsize:cmdhist:expand_aliases:extquote:force_fignore:histappend:hostcomplete:interactive_comments:login_shell:progcomp:promptvars:sourcepath BASH_ALIASES=() BASH_ARGC=() BASH_ARGV=() BASH_CMDS=() BASH_LINENO=() BASH_SOURCE=() ... -
常见的环境变量
PWD、PATH、USER、LOGNAME、UID、SHELL、HOME、PS1、PS2
比如:
[root@node3 ~]# echo $PS1 [\u@\h \W]$ #列出命令行提示符
位置变量:
像上面这样的脚本程序只能执行一些预先定义好的功能,未免太过死板。为了让Shell脚本程序更好地满足用户的一些实时需求,以便灵活完成工作,必须要让脚本程序能够像之前执行命令时那样,接收用户输入的参数。
其实,Linux系统中的Shell脚本语言早就考虑到了这些,已经内设了用于接收参数的变量,变量之间使用空格间隔。例如:
$0对应的是当前Shell脚本程序的名称,
$#对应的是总共有几个参数,
$对应的是所有位置的参数值,
$?对应的是显示上一次命令的执行返回值,
而2、$3……则分别对应着第N个位置的参数值,
由了概念之后,来实践一下:
[root@node3 ~]# vi aa.sh #写个Shell脚本
#!/bin/bash
echo $#
echo $0
echo $*
echo $6
[root@node3 ~]# bash aa.sh 1 2 3 4 5 6
6
aa.sh
1 2 3 4 5 6
6
预定义变量:
- 用来保存脚本程序的执行信息
- 不能直接为这些变量赋值
| 变量名 | 含义 |
|---|---|
| $0 | 当前所在的进程或脚本名 |
| $$ | 当前运行进程的PID号 |
| $? | 命令执行后的返回状态值,0表示正常,1或其它表示异常 |
| $# | 以加载的位置变量个数 |
| $* | 所有位置变量的值 |
[root@node3 ~]# echo $$
33689
[root@node3 ~]# echo $?
0
[root@node3 ~]# echo $#
0
[root@node3 ~]# echo $*
[root@node3 ~]#
变量值及范围控制
在写Shell脚本时经常需要给变量赋值,有的时候会遇见特殊情况,比如:变量值中包含空格,$,等特殊标记,再者需要在变量值中插入个换行...那么如果想要实现这些效果,仅靠前面的:变量名=变量值的写法是不行的。所以还需要学习更加高级的功能
引号在赋值中的应用
引号分为单引号和双引号
单引号'':
- 所有的字符均视为该字符本身(无特殊含义)
- 不允许\转义
双引号"":
- 在双引号被允许$扩展,可调用其它变量的值
- 出现特殊字符时,可采用\符号转义
- 当变量不包含空格、制表符、双引号通常被省略
双引号赋值案例:
当变量值中有空格时
[root@node3 ~]# A=1
[root@node3 ~]# B=$A 2 #可以发现,当变量值有空格时,直接赋值给B会报错
-bash: 2: command not found
[root@node3 ~]# B="$A 2" #此时加上"",就不会报错了
[root@node3 ~]# echo $B #正确输出
1 2
当变量值中特殊字符时
[root@node3 ~]# A="a\tb\nc"
[root@node3 ~]# echo -e $A #-e选项解析特殊转义符
a b
c
[root@node3 ~]# fn="\"" #\可以把特殊字符转为普通字符
[root@node3 ~]# echo $fn
"
单引号赋值:
保留特殊字符
[root@node3 ~]# A=CentOS
[root@node3 ~]# B='$A Server'
[root@node3 ~]# echo $B
$A Server #可以看到$A并每有引用变量值,而是转为特殊字符
read读入变量值
在有些情况下,我们也需要使用交互式来获取信息,比如:用户登录需要验证的程序,创建备份文件时,备份文件名称需要用户自定义...那这时就需要个变量,来保存用户输入的信息
直接案例
[root@node3 ~]# read YOUR_NAME
aa
[root@node3 ~]# echo $YOUR_NAME
aa
#但是上面的那种方法有点空洞,好像少点提示信息,我们可以家伙是那个-p参数来实现
[root@node3 ~]# read -p "bo login:" YOUR_AA
bo login:aa #这样就很人性化
[root@node3 ~]# echo $YOUR_AA
aa
#但是呢,虽然上面的-p参数人性化了,也会发现问题,当输入信息时,它不安全,因为我们为输入的信息是现实在屏幕上的,那怎样才能不让它显示我们输入的信息呢?
#加上个-s参数即可
[root@node3 ~]# read -s -p "Password:" YOUR_PASS
Password:[root@node3 ~]#
[root@node3 ~]# echo $YOUR_PASS
123
变量的作用范围
在Linux命令行当中,如果用户张三定义个变量,那么李四用户是否能引用呢?如果root用户在登录之后,新建一个变量它新开启的子程序里面是否也能用呢?要解决这几个问题,就需要了解变量的作用范围
变量的作用范围分为两种:
-
局部变量
只在定义此变量的当前Shell环境下有效
自定义变量默认都是局部变量
那所以张三定义的变量,李四是引用不了的
-
全局变量
在当前Shell及所有的子Shell环境下都有效
子Shell中若赋值同名变量,与父Shell中的变量无关
现在已经知道了全局变量和局部变量了,那么我们如何来控制全局变量和局部变量呢?比如:将局部变量变为全局变量,或者说将全局变量取消到局部变量?
只需要一个命令即可:export
export 局部变量=变量值
直接定义/赋值指定的变量,作为全局变量
对已有的局部变量,只需发布不需要赋值
案例:
[root@node3 ~]# x=123 #用户自定义,属于局部变量
[root@node3 ~]# y=456 #用户自定义,属于局部变量
[root@node3 ~]# export y #将局部变量变为全局变量
[root@node3 ~]# echo $x $y #在原来的shell环境引用x,y变量,都能引用
123 456
[root@node3 ~]# bash #开个子Shell
[root@node3 ~]# echo $x $y #可以看到引用x,y变量,最终只有y变量出结果
456
既然有指定就有取消,所以取消全局变量的命令:
export -n 全局变量名
取消变量的全局属性
此操作对局部变量无实际意义
[root@node3 ~]# exit #退回原来的子Shell
exit
[root@node3 ~]# export -n y #取消y的全局属性
[root@node3 ~]# bash #在进入子Shell
[root@node3 ~]# echo $y #进行引用,
[root@node3 ~]#
数值运算及处理
基本运算类别
四则运算:
- 加法:num1 + num2
- 减法:num1 - num2
- 乘法:num1 * num2
- 整除:num1 / num2
取余数运算
- 求模:num1%num2
计算并获取结果
使用expr命令行工具
- 格式:expr 整数1 运算符 整数2,运算符和数字之间需要有空格
- 乘法操作应采用*转义,避免被作为Shell通配符
- 计算结果直接显示在屏幕上
[root@node3 ~]# x=48 ; y=21
[root@node3 ~]# expr $x + 100 - $y * 2 / 7
142
[root@node3 ~]# expr $x / $y #除法,取整数
2
[root@node3 ~]# expr $x % $y #除法,取余
6
可以看到使用expr命令,有点复杂,我们可以使用$[]表达式
- 格式:$[整数1 运算符 整数2]
- 乘法操作无需转义,运算符两侧可以无空格,引用变量可省略$符号
- 计算结果替换吊打是本身,结合echo命令才能显示到屏幕上
[root@node3 ~]# echo $[2**4] #表示乘方
16
[root@node3 ~]# echo $[x+100-y*2/7]
142
[root@node3 ~]# echo $[x/y]
2
[root@node3 ~]# echo $[x%y]
6