目录
前言
一、Shell脚本语言的基本结构
1、Shell脚本的用途:
2、 Shell脚本基本结构:
3、 创建Shell脚本过程
4、 脚本注释规范
5、第一个脚本
6、 执行脚本
7、脚本错误
二、Shell脚本语言的变量用法详解
1、变量
2、 变量类型
3、 Shell中变量命名法则
4、 变量定义和引用
5、 环境变量
6、只读变量
7、位置变量
8、 退出状态码变量
9、 展开命令行
9.1展开命令执行顺序
9.2 防止扩展
9.4 变量扩展
10、 脚本安全和set(暂时不用)
10.1 $-变量
10.2 set命令实现脚本安全
三、 Shell字符串详解
2、Shell字符串截取
3、汇总
4、Shell的格式化输出printf
4.1、语法格式:
4.2、常用格式替换符:
4.3、常用转义字符:
四、Shell脚本语言的运算
4.1 算数运算
4.2 Bash中的算术运算
4.2 实现算术运算
4.3 增强型赋值:
4.2 逻辑运算(了解,不用掌握)
4.3 短路运算(不用了解)
结语
在已经学习了关于Linux系统入门,掌握一定的命令如何使用之后,接下来就是第一阶段的重点,也就是如何编写Shell脚本。
编程语言分类
静态和动态语言:
静态编译语言:使用变量前,先声明变量类型,之后类型不能改变,在编译时检查,如Java语言、C语言
动态编译语言:不事先声明,可随时改变类型,如Shell语言、python语言、javascript语言、php语言
强类型和弱类型语言:
强类型语言:不同类型数据操作,必须经过强制转换成同一类型才能运算,如java,C#
弱类型语言:语言的运行时会隐式地做数据类型转换。无需指定类型,默认均为字符型;参与计算会自动进行隐式类型转换;变量无需事先定义可直接调用。如Shell语言,php,JavaScript
脚本检查工具:
yum install -y epel-release
yum install -y ShellCheck
自动化常用命令
执行系统管理和故障排除
创建简单的应用程序
处理文本或文件
Shell脚本编程:是基于过程式,解释执行的语言
编程语言的基本结构:
各种系统命令的组合
数据存储:变量,数组
表达式:a+b
控制语句:if、case、for、while
shell脚本:包含一些命令或声明,并符合一定格式的文本文件
格式要求:首行执行shebang机制
#声明后续语句是通过那种语言写的
#!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl
使用vim创建文本文件,第一行必须包括shell声明序列:
#!/bin/bash
加执行权限,给予执行权限,在命令行上指定脚本的绝对或相对路径
[root@arthur ~]# chmod +x shellScript/hello.sh
运行脚本,直接运行解释器,将脚本作为解释器程序的参数运行
[root@arthur ~]# /root/shellScript/hello.sh
第一行一般为调用使用的语言
程序名,避免更改文件名为无法找到正确的文件
版本号
更改后的时间
作者相关信息
该程序的作用,及注意事项
最后是各版本的更新简要说明
[root@arthur ~]# cat shell/hostname.sh
#!/bin/bash
echo "My hostname is `hostname`"
echo "Time is `date +'%F %T'`"
[root@arthur ~]# chmod +x shell/hostname.sh
[root@arthur ~]# shell/hostname.sh
增加执行权限,执行脚本时会创建一个子shell,不影响现有的shell环境
chmod +x sh
./sh
/root/shellScript/sh
使用 . 或者source,执行脚本时不会创建一个子shell,会影响现有的shell环境
source sh
. sh
注意:尽量不要使用该方式执行脚本!!!
实例:备份脚本
[root@arthur ~]# mkdir /backup/
[root@arthur ~]# vim shell/backup.sh
[root@arthur ~]# cat shell/backup.sh
#!/bin/bash
echo -e "\033[1;32m\033[0m"
sleep 2
cp -av /etc/ /backup/etc`date +%F`/
echo -e "\033[1;32mBackup is finished\033[0m"
[root@arthur ~]# chmod +x shell/backup.sh
[root@arthur ~]# ./shell/backup.sh
Starting backup..
...
Backup is finished
语法错误,会导致后续的命令不继续执行,可以用bash -n shellname检查错误
命令错误,后续的命令还会继续,可以使用bash -x shellname检查
逻辑错误,只能使用bash -x进行观察
变量表示命名的内存空间,将数据放在内存空间中,通过变量名引用,获取数据
变量类型:
内置变量:如PS1,PATH,UID,HOSTNAME,HISTSIZE
用户自定义变量
预定义变量
位置变量
不同的变量存放的数据不同,决定了以下:
数据存储方式
参与的计算
表示的数据范围
变量数据类型:
字符串
数值:整型,浮点型(小数)、bash不支持浮点数
不能使用程序中的保留字,如:if,for
只能使用数字,字母及下划线,且不能以数字开头
见名思意,用英文名字,并体现真正含义
统一命名规则:驼峰命名法
全局变量名大写
局部变量小写
函数名小写
变量的生效范围(变量作用域)
普通变量:生效范围为当前shell进程;对当前shell之外的其他shell进程,包括当前shell的子shell进程均无效
环境变量:生效范围为当前shell进程及其子进程
本地变量:生效范围为当前shell进程中某代码片段,通常指函数
变量赋值:
name="value"
value可以是以下多种类型
直接字符串:name='root'
变量引用:name="$USER"
命令应用:name=`command` || name=$(command)
通配符:FILE=/etc/* /*表示etc目录下所有的文件名*/
注意:变量赋值是临时生效,当退出终端后,变量会自动删除,无法持久保存。
变量引用:
$name
${name}
弱引用和强引用:
“$name”:弱引用,其中的变量引用会被替换成为变量值
‘$name’:强引用,其中的变量引用不会被替换成变量值,而保持原字符串
实例
[root@arthur ~]# name='Mike'
[root@arthur ~]# NAME="$USER"
[root@arthur ~]# hostname=`hostname`
[root@arthur ~]# echo "My name is $name"
My name is Mike
[root@arthur ~]# echo "My name is $NAME"
My name is root
[root@arthur ~]# echo "My hostname is $hostname"
My hostname is localhost.localdomain
[root@arthur ~]#
[root@arthur ~]# NUM=`seq 10`
[root@arthur ~]# echo $NUM
1 2 3 4 5 6 7 8 9 10
[root@arthur ~]# echo "$NUM"
1
2
3
4
5
6
7
8
9
10
[root@arthur ~]#
查看已定义的所有变量
[root@arthur ~]#set
删除变量
[root@arthur ~]#unset shellname1 shellname2
//实例:
[root@arthur ~]# echo $name
Mike
[root@arthur ~]# unset name
[root@arthur ~]# echo $name
[root@arthur ~]#
实例:"{}"的使用
[root@arthur ~]# NAME=mage
[root@arthur ~]# AGE=20
[root@arthur ~]# echo $NAME
mage
[root@arthur ~]# echo $AGE
20
[root@arthur ~]# echo $NAME $AGE
mage 20
[root@arthur ~]# echo $NAME_$AGE
20
[root@arthur ~]# echo ${NAME}_$AGE
mage_20
[root@arthur ~]#
显示系统信息
[root@arthur ~]# vim shell/OS.sh
#!/bin/bash
RED="\E[1;31m"
GREEB="\E[1;32m"
END="\E[0m"
echo -e "\E[1;32m----------Host systeminfo----------$END"
echo -e "HOSTNAME: $RED `hostname`$END"
echo -e "IPADDR: $RED `ifconfig ens160 | grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -n1` $END"
echo -e "OSVERSION: $RED `cat /etc/redhat-release`$END"
echo -e "KERNEL: $RED `uname -r`$END"
echo -e "CPU: $RED `lscpu | grep "型号名称:" | tr -s ' ' ' '|cut -d ' ' -f 2-5` $END"
echo -e "MEMORY: $RED `free -h | grep Mem | tr -s ' ' ' '|cut -d ' ' -f 4` $END"
echo -e "DISK: $RED `lsblk | grep '^sda' | tr -s ' ' | cut -d ' ' -f 4` $END"
echo -e "\E[1;32m---------- END ----------$END"
[root@localhost ~]# chmod +x shell/OS.sh
[root@localhost shellScript]# ./os.sh
----------Host systeminfo----------
HOSTNAME: localhost.localdomain
IPADDR: 19168.115.213
OSVERSION: CentOS Linux release 7.9.2009 (Core)
KERNEL: 3.10.0-1160.el7.x86_64
CPU: 11th Gen Intel(R) Core(TM)
MEMORY: 1.0G
DISK: 200G
---------- END ----------
利用变量实现动态命令
[root@arthur ~]# CMD=hostname
[root@arthur ~]# $CMD
localhost.localdomain
[root@arthur ~]#
环境变量:
可以使子进程(包括孙子进程)继承父进程的变量,但是无法让父进程使用子进程的变量。
一旦子进程修改了从父进程继承的变量,将会传递新的值给孙子进程
一般只在配置文件中使用,在脚本中较少使用
普通变量生效的范围与环境变量生效的范围
[root@arthur ~]# vim shell/father.sh
#!/bin/bash
NAME=father
echo "father.sh:NAME=$NAME"
echo "fatther is PID=$BASHPID"
shell/son.sh
[root@arthur ~]#vim shell/son.sh
#!/bin/bash
echo "son.sh:NAME=$NAME"
NAME=son
echo "son.sh:NAME=$NAME"
echo "son.sh PID is $BASHPID"
echo "son.sh father pid is $PPID"
sleep 100
[root@arthur ~]#chmod -R +x shell/*
[root@arthur ~]# ./shell/father.sh
father.sh:NAME=father
fatther is PID=12053
son.sh:NAME=
son.sh:NAME=son
son.sh PID is 12054
son.sh father pid is 12053
#子进程无法使用父进程的变量,需要自己赋值
变量声明和赋值:
export name=VALUE
declare -x name =VALUE
#或者先赋值再声明~
value可以是以下多种类型
直接字符串:name='root'
变量引用:name="$USER"
命令应用:name=`command` || name=$(command)
通配符:FILE=/etc/* /*表示etc目录下所有的文件名*/
declare命令详解:(了解)
declare 为 shell 指令,在第一种语法中可用来声明变量并设置变量的属性([rix]即为变量的属性),在第二种语法中可用来显示 shell 函数。若不加上任何参数,则会显示全部的 shell 变量与函数(与执行 set 指令的效果相同)
语法
declare [+/-][rxi][变量名称=设置值] 或 declare -f
参数说明:
+/- "-“可用来指定变量的属性,”+"则是取消变量所设的属性。
-f 仅显示函数
r 将变量声明为只读变量。注意,一旦设置为只读变量,既不能修改变量的值也不能删除变量,甚至不能通过+r取消只 读属性
x 指定的变量会成为环境变量,可供shell以外的程序来使用
i 将变量声明为整数型(integer)
p 显示指定变量的被声明类型。
实例
声明整数型变量
#!/bin/bash
declare -i ab //声明整数型变量
ab=56 //改变变量内容
echo $ab //显示变量内容
--->
56
改变变量属性
#!/bin/bash
#声明整数型变量
declare -i ef
ef=1
echo $ef
#变量赋值,赋予文本值
ef="wer"
echo $ef
#取消变量属性
declare +i ef
ef="wer"
echo $ef
设置变量只读
#!/bin/bash
declare -r ab #设置变量为只读
ab=88 #改变变量内容
echo $ab #显示变量内容
--->
declare.sh:行3: ab: 只读变量
声明数组变量
#!/bin/bash
#声明数组变量
declare -a cd
cd[0]=a
cd[1]=b
cd[2]=c
#输出数组的指定内容
echo ${cd[1]}
#显示整个数组变量内容
echo ${cd[@]}
显示函数
#!/bin/bash
#声明函数
declare -f
function command_not_found_handle(){
if [ -x /usr/lib/command_not_found_handle ];then
/usr/bin/python /usr/lib/command_not_found_handle -- $1;
return $?;
else
if [ -x /usr/share/command_not_found_handle ];then
/usr/bin/python /usr/share/command_not_found_handle --$1;
return $?;
else
return 127;
fi;
fi;
}
变量引用:
$name
${name}
实例运用:
[root@arthur ~]# vim shell/father.sh
#!/bin/bash
NAME=father
export NAME
echo "father.sh:NAME=$NAME"
echo "fatther is PID=$BASHPID"
shell/son.sh
[root@arthur ~]#vim shell/son.sh
#!/bin/bash
echo "son.sh:NAME=$NAME"
NAME=son
echo "son.sh:NAME=$NAME"
echo "son.sh PID is $BASHPID"
echo "son.sh father pid is $PPID"
sleep 100
[root@arthur ~]#chmod -R +x shell/*
[root@arthur ~]# ./shell/father.sh
father.sh:NAME=father
fatther is PID=12053
son.sh:NAME=father
son.sh:NAME=son
son.sh PID is 12054
son.sh father pid is 12053
[root@arthur ~]# ./shell/father.sh
father.sh:NAME=father
fatther is PID=12142
son.sh:NAME=father
son.sh:NAME=son
son.sh PID is 12143
son.sh father pid is 12142
#父进程定义了一个环境变量,在子进程上可以进行调用
#父进程无法使用子进程的变量
#子进程自己定义了一个同名变量,就覆盖环境变量
显示所有环境变量:
[root@arthur ~]# env
[root@arthur ~]# printenv
[root@arthur ~]# export
[root@arthur ~]# declare -x
删除变量
[root@arthur ~]#unset shellname1 shellname2
Bash内建的环境变量
PATH
SHELL
USER
UID
PWD
SHLVL #shell的嵌套层数,即深度
LANG
MAIL
HOSTNAME
HISTSIZE
_ #下划线,表示前一命令的最后一个参数
只读变量:只能声明定义,但后续不能修改和删除
声明只读变量:
readonly name
declare -r name
查看只读变量:
readonly [-p]
declare -r
位置变量:在Bash Shell中内置的变量,在脚本代码中调用命令行传递给脚本的参数
$1,$2,... 对应第一个,第二个等参数,shift[n]换位置,最多9个
#预定义变量
$0 命令本身,包括路径
$* 传递给脚本的所有参数,全部参数合成一个字符串
$@ 传递给脚本的所有参数,每个参数为独立字符串
$# 传递给脚本的参数的个数
$? 上个命令的退出状态,或函数的返回值
$$ 当前shell进程ID。对于Shell脚本,就是这些脚本所在的进程ID
注意:$@,$*只有被双引号括起来的时候才会有差异
清空所有位置变量
set --
//写在脚本内部
实例演示1:
[root@arthur ~]# vim shell/ARG.sh
#!/bin/bash
echo "1st arg is $1"
echo "2st arg is $2"
echo "3st arg is $3"
echo "4st arg is $4"
echo "The number of are is $#"
echo "All args are $*"
echo "All args are $@"
echo "The scriptname is `basename $0`"
[root@arthur ~]# chmod +x shell/ARG.sh
[root@arthur ~]# shell/ARG.sh {1..10}
1st arg is 1
2st arg is 2
3st arg is 3
4st arg is 4
The number of are is 10
All args are 1 2 3 4 5 6 7 8 9 10
All args are 1 2 3 4 5 6 7 8 9 10
The scriptname is ARG.sh
[root@arthur ~]#
实例演示:编写一个移动文件脚本
[root@arthur ~]# vim shell/move.sh
#!/bin/bash
WANGING_COLOR="echo -e \E[1;31m"
END="\E[0m"
DIR=/tmp/`date +%F_%H-%M-%S`
mkdir $DIR
mv $* $DIR
${WANGING_COLOR} MOVE $* to $DIR $END
[root@arthur ~]# chmod +x shell/move.sh
[root@arthur ~]# touch {a,b,c}
[root@arthur ~]# ls
a anaconda-ks.cfg b c shell
[root@localhost ~]# shell/move.sh a b c
MOVE a b c to /tmp/2022-08-16_10-07-55
[root@arthur ~]# tree /tmp/
/tmp/
└── 2022-08-16_10-07-55
├── a
├── b
└── c
1 directory, 3 files
[root@arthur ~]#
进程执行后,将使用变量 ? 保存状态码的相关数字,不同的值反应成功与失败, ?保存状态码的相关数字,不同的值反应成功与失败, ?保存状态码的相关数字,不同的值反应成功与失败,的取值范围为[0,255]
$?的值为0 代表成功
$?的值不为0 代表失败
用户可以在脚本中使用以下命令自定义退出状态码
exit [n]
实例:
[root@arthur~]# ping -c 2 www.baidu.com > /dev/null
[root@arthur ~]# echo $?
0
[root@arthur ~]# cmd
-bash: cmd: 未找到命令
[root@arthur ~]# echo $?
127
[root@arthur ~]#
注意:
脚本中一旦遇到了exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字
如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码
实例:$?获取上一个命令的退出状态
#!/bin/bash
if [ "$1"==100 ];then
exit 0 #参数正确,退出状态为0
else
exit 1 #参数错误,退出状态为1
fi
exit
表示退出当前 Shell 进程,我们必须在新进程中运行 test.sh,否则当前 Shell 会话(终端窗口)会被关闭,我们就无法取得它的退出状态了
[root@arthur re_study]# bash test.sh 100
[root@arthur re_study]# echo $?
0
[root@arthur re_study]# bash test.sh 99
[root@arthur re_study]# echo $?
1
[root@arthur re_study]#
实例:$?获取函数的返回值
#!/bin/bash
#得到两个数相加的和
function add(){
return `expr $1 + $2`
}
add 23 50 #调用函数
echo $? #获取函数返回值
# 运行结果:
[root@arthur re_study]# bash test.sh
73
把命令行分成单个命令词
展开别名
展开大括号的声明{}
展开波浪符声明(~)
命令替换$()和``
再次把命令行分成命令词
展开文件通配(*,?,[abc]等)
准备I/O重导向(<,>)
运行命令
反斜线(\)会使随后的字符按原意解释
实例:
单引号(' ')防止所有扩展
双引号(" ")可防止扩展,但是以下清空例外:$(美元符号)
``:反引号,命令替换
\:反斜线,禁止单个字符扩展
!:叹号,历史命令替换
set命令:可以用来定制shell环境
h:hashell,打开选项后,Shell会将命令所在的路径hash下来,避免每次都要查询。通过set +h将h选项关闭.默认开启
i:interactive-comments,包含这个选项说明当前的shell是一个交互式的shell。所谓的交互式shell,在脚本中,i选项是关闭的
m:monitor,打开监控模式,就可以通过Job control来控制进程的停止,继续,后台或者前台执行等
B:braccexpand,大括号扩展
H:history,H选项打开,可以展开历史列表中的命令,可以通过!
来完成,例如!!
返回最近的一个历史命令,!n
返回第n个历史命令
实例:
[root@arthur ~]# echo $-
himBHs
[root@arthur ~]# hash
命中 命令
1 /usr/sbin/ping
[root@arthur ~]# set +h
[root@arthur ~]# echo $-
imBHs
[root@arthur ~]# hash
-bash: hash: 已禁用哈希
[root@arthur ~]#
[root@arthur ~]# echo {1..10}
1 2 3 4 5 6 7 8 9 10
[root@arthur ~]# set +B
[root@arthur ~]# echo {1..10}
{1..10}
[root@arthur ~]#
[root@arthur ~]# set +H
[root@arthur ~]# history !!
-bash: history: !!: 需要数字参数
[root@arthur ~]# history !3
-bash: history: !3: 需要数字参数
[root@arthur ~]# set -H
[root@arthur ~]# history !!
history set -H
-bash: history: set: 需要数字参数
[root@arthur ~]# history !3
history ifconfg
-bash: history: ifconfg: 需要数字参数
[root@arthur ~]#
-u:在扩展一个没有设置的变量时,显示错误信息,等同于set -o nounset
-e:如果一个命令返回一个非0退出的状态值(失败)就退出,等同于set -o errexit
-o:option 显示,打开或关闭选项
显示选项:set -o
打开选项:set -o 选项
关闭选项:set +o 选项
-x:当执行命令时,打印命令及其参数,类似bash -x
实例:
[root@arthur ~]# set -o
allexport off
braceexpand off
emacs on
errexit off
errtrace off
functrace off
hashall on
histexpand on
history on
ignoreeof off
interactive-comments on
keyword off
monitor on
noclobber off
noexec off
noglob off
nolog off
notify off
nounset off
onecmd off
physical off
pipefail off
posix off
privileged off
verbose off
vi off
xtrace off
[root@arthur ~]# vim shell/error.sh
#!/bin/bash
echo "Hello World"
sssss
echo "Hello Linux"
[root@arthur ~]# chmod +x shell/error.sh
[root@arthur ~]# shell/error.sh
Hello World
shell/error.sh:行3: sssss: 未找到命令
Hello Linux
[root@arthur ~]# vim shell/error.sh
#!/bin/bash
set -e
echo "Hello World"
sssss
echo "Hello Linux"
[root@arthur ~]# shell/error.sh
Hello World
shell/error.sh:行4: sssss: 未找到命令
[root@arthur ~]#
字符串(String)就是一系列字符的组合。字符串是Shell编程中最常用的数据类型之一
字符串可以由单引号''
包围,也可以由""
包围,也可以不用引号,三种方式的区别
由单引号' '
包围的字符串
任何字符都会原样输出,在其中使用变量是无效的
字符串中不能出现单引号,即使对单引号进行转义也不行
由双引号" "
包围的字符串
如果其中包含了某个变量,那么该变量就会被解析(得到该变量的值),而不是原样输出
字符串中可以出现双引号,只要进行转义就行
不被引号包围的字符串
不被引号包围的字符串中出现变量也会被解析,这一点和双引号""
包围的字符串一样
字符串中不能出现空格,否则空格后面的字符串会作为其他变量或者命令解析
通过代码演示一下三种形式的区别
#!/bin/bash
n=74
str1=c.biancheng.net$n
str2="shell \"Script\" $n"
str3='C语言中文网 $n'
echo $str1
echo $str2
echo $str3
# 运行结果
c.biancheng.net74
shell "Script" 74
C语言中文网 $n
str1 中包含了$n
,它被解析为变量 n 的引用。$n
后边有空格,紧随空格的是 str2;Shell 将 str2 解释为一个新的变量名,而不是作为字符串 str1 的一部分
str2 中包含了引号,但是被转义了(由反斜杠\
开头的表示转义字符)。str2 中也包含了$n
,它也被解析为变量 n 的引用
str3 中也包含了$n
,但是仅仅是作为普通字符,并没有解析为变量 n 的引用
获取字符串长度
在Shell中获取字符串长度很简单,具体方法如下:
${#string_name}
string_name:表示字符串名字
在脚本语言中,字符串的拼接(也称为字符串连接或者字符串合并)往往都非常简单,例如:
在PHP
中使用.
即可连接两个字符串
在JavaScript
中使用+
即可将两个字符串合并为一个
然而,在Shell中你不需要使用任何运算符,将两个字符串并排放在一起就能实现拼接
#!/bin/bash
name="shell"
url="http://c.biancheng.net/shell/"
str1=$name$url #中间不能有空格
str1=$name":"$url
str2="$name $url" #如果被双引号包围,那么中间可以有空格,也可以出现别的字符串
str3="$name:$url"
str4="${name}Script:${url}Index.html" #在变量后加上字符串,需要给变量名加上大括号
Shell截取字符串通常有两种方式,从指定位置开始截取和从指定字符(子字符串)开始截取
从指定位置开始截取
这种方式需要两个参数:除了指定起始位置,还需要截取长度,才能最终确定要截取的字符串
既然需要指定起始位置,那么就要涉及到计数方向的问题,到底是从字符串左边开始计数,还是从字符串右边开始计数?答案是:Shell同时支持两种计数方式
1.从字符串左边开始计数
如果想从字符串的左边开始计数,那么截取字符串的具体格式如下
${string:start:length}
`其中,Sting是要截取的字符串,start是起始位置(从左边开始,从0开始计数),length是要截取的长度(省略的话表示直到字符串的末尾)
例如:
url="c.biancheng.net"
echo ${url:2:9}
>结果为:biancheng
url="c.biancheng.net"
echo ${url:2} #省略length,截取到字符串末尾
>结果为:biancheng.net
2.从右边开始计数
如果想从字符串的右边开始计数,那么截取字符串的具体格式如下:
${string:0-start:length}
`同第 1) 种格式相比,第 2) 种格式仅仅多了0-,这是固定的写法,专门用来表示从字符串右边开始计数。
这里需要强调两点:
从左边开始计数时,起始数字是 0(这符合程序员思维);
从右边开始计数时,起始数字是 1(这符合常人思维)
计数方向不同,起始数字也不同。
不管从哪边开始计数,截取方向都是从左到右。
例如:
url="c.biancheng.net"
echo ${url:0-13:9}
>结果为:biancheng 从右边数:b是第13个字符
url="c.biancheng.net"
echo ${url:0-13} #省略length,直接截取到字符串末尾
>结果为:biancheng.net
从指定字符(子字符串)开始截取
这种截取方式无法指定字符串长度,只能从指定字符(子字符串)截取到字符串末尾。Shell可以截取指定字符(子字符串)右边的所有字符,也可以截取左边的所有字符
1)使用#号截取右边字符
使用#
号可以截取指定字符(或子字符串)右边的所有字符,具体格式如下:
${string#*chars}
#其中,string 表示要截取的字符,chars 是指定的字符(或者子字符串),*是通配符的一种,表示任意长度的字符串。*chars连起来使用的意思是:忽略左边的所有字符,直到遇见 chars(chars 不会被截取)
例如:
url="http://c.biancheng.net/index.html"
echo ${url#*:}
>结果为://c.biancheng.net/index.html
echo ${url#*p:}
echo ${url#*ttp:}
`如果不需要忽略chars左边的字符,那么也可以不写*
url="http://c.biancheng.net/index.html"
echo ${url#http://}
>结果为:c.biancheng.net/index.html
`注意:以上写法遇到第一个匹配的字符(子字符串)就结束了
url="http://c.biancheng.net/index.html"
echo ${url#*/}
>结果为:/c.biancheng.net/index.html。url 字符串中有三个/,输出结果表明,Shell 遇到第一个/就匹配结束了
使用##
可以直到最后一个指定字符(子字符串)再匹配结束
${string##*chars}
例如:
#!/bin/bash
url="http://c.biancheng.net/index.html"
echo ${url#*/}
# 结果为:/c.biancheng.net/index.html
echo ${url##*/}
# 结果为:index.html
str="-----aa+++aa@@@"
echo ${str#*aa}
# 结果为:+++aa@@@
echo ${str##*aa}
# 结果为:@@@
2)使用%截取左边字符
使用%
号可以截取指定字符(或者子字符串)左边的所有字符
${string%chars*}
`请注意*的位置,因为要截取 chars 左边的字符,而忽略 chars 右边的字符,所以*应该位于 chars 的右侧。其他方面%和#的用法相同
#!/bin/bash
url="http://c.biancheng.net/index.html"
echo ${url%/*} #结果为 http://c.biancheng.net
echo ${url%%/*} #结果为 http:
str="---aa+++aa@@@"
echo ${str%aa*} #结果为 ---aa+++
echo ${str%%aa*} #结果为 ---
格式 | 说明 |
${string:start :length} | 从string字符串的左边第start个字符开始,向右截取length个字符。 |
${string:start} | 从string字符串的左边第start个字符开始截取,直到最后。 |
${string:0-start:length} | 从string字符串的右边第start个字符开始,向右截取length个字符。 |
${string:0-start} | 从string字符串的右边第start个字符开始截取,直到最后。 |
${string#*chars} | 从string字符串第一次出现chars的位置开始,截取chars右边的所有字符。 |
${string##*chars} | 从string字符串最后一次出现chars的位置开始,截取chars右边的所有字符。 |
${string##*chars} | 从string字符串第一次出现chars的位置开始,截取chars左边的所有字符。 |
${string%%chars*} | 从string字符串最后一次出现chars的位置开始,截取chars左边的所有字符。 |
printf "指定的格式" "文本1" "文本2" .....
替换符 | 功能 |
%s | 字符串 |
%f | 浮点格式,保留小数点位数%.nf,n为数字 |
%b | 相对应的参数中包括转义字符时,可以使用此替换符进行替换,对应的转义字符会被转义 |
%c | ASCII字符,即显示对应参数的第一个字符 |
%d,%i | 十进制整数 |
%o | 八进制值 |
%u | 不带正负号的十进制值 |
%x | 十六进制值(a-f) |
%X | 十六进制值(A-F) |
%% | 表示%本身 |
说明:%s中的数字代表此替换符中的输出字符宽度,不足补空格,默认是右对齐,%-10s表示10个字符宽,-表示左对齐
转义符 | 功能 |
\a | 警告字符,通常为ASCII的BEL字符 |
\b | 后退 |
\f | 换页 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\\ | 表示\本身 |
实例
[root@arthur ~]# printf "%s\n" 1 2 3 4
1
2
3
4
[root@arthur ~]# printf "%f\n" 1 2 3 4
1.000000
2.000000
3.000000
4.000000
[root@arthur ~]# printf "%.2f\n" 1 2 3 4 #.2f表示保留两位小数
1.00
2.00
3.00
4.00
[root@arthur ~]# printf "(%s)" 1 2 3 4;echo " "
(1)(2)(3)(4)
[root@localhost ~]# printf " (%s) " 1 2 3 4;echo " "
(1) (2) (3) (4)
[root@arthur ~]# printf " (%s) (%s)\n" 1 2 3 4;echo " "
(1) (2)
(3) (4)
[root@arthur ~]# printf " %s %s\n" 1 2 3 4;echo " "
1 2
3 4
[root@arthur ~]# printf "%s %s %s\n" 1 2 3 4
1 2 3
4
[root@arthur ~]#
#%-10s表示宽度10个字符,左对齐
[root@arthur ~]# printf "%-10s %-10s %-4s %s \n" 姓名 性别 年龄 体重 小明 男性 20岁 70KG 小红 女性 18岁 50KG
姓名 性别 年龄 体重
小明 男性 20岁 70KG
小红 女性 18岁 50KG
[root@arthur ~]#
#将十进制的1000转换为16进制数
[root@arthur ~]# printf "%X\n" 1000
3E8
[root@arthur ~]# printf "%x\n" 1000
3e8
[root@arthur ~]#
#将十六进制的C转换为十进制
[root@arthur ~]# printf "%d\n" 0xc
12
[root@arthur ~]#
[root@arthur ~]# var="welcome to study";printf "\033[31m%s\033[0m\n" $var
welcome
to
study
[root@arthur ~]# var="welcome to study";printf "\033[31m%s\033[0m\n" "$var"
welcome to study
[root@arthur ~]#
shell支持算术运算,但只支持整数,不支持小数
-- + 加法运算
-- - 减法运算
-- * 乘法运算
-- / 除法运算
-- % 取模,即取余数
-- ** 乘方
#乘法符号在有些场景需要转义
1. let var=算术表达式
2. var=$[算术表达式]
3. var=$((算术表达式))
4. var=$(expr arg1 arg2 arg3 ...)
5. declare -i var = 数值
6. echo '算术表达式' | bc (支持浮点数)
实例:使用bc计算小数和declare -i计算
[root@arthur ~]# echo "scale=3;20/3"|bc
6.666
[root@arthur ~]# echo "scale=3;2/3"|bc
.666
[root@arthur ~]# i=20
[root@arthur ~]# j=20
[root@arthur ~]# declare -i sum=i*j
[root@arthur ~]# echo $sum
400
[root@arthur ~]#
内建的随机数生成器变量:
$RANDOM 取值范围:0-32767
实例:生成0-49之间的随机数
[root@arthur ~]# echo $[$[$RANDOM%50]+1]
40
[root@arthur ~]# echo $[$RANDOM%50]
44
[root@arthur ~]# echo $[$[$RANDOM%50]+1] #生成1~50之间的随机数
[root@arthur ~]# echo $[RANDOM % 100 + 1]
实例:生成随机颜色字符串
[root@arthur ~]# echo -e "\033[1;$[RANDOM%7+31]mStudy\033[0m"
Study
[root@arthur ~]# echo -e "\033[1;$[RANDOM%7+31]mStudy\033[0m"
Study
[root@arthur ~]#
+=
i+=10 <==> i=1+10
-=
i-=j <==> i=i-j
*=
/=
%=
++
i++,++1 <==> i=i+1 (自增)
--
i--,--i <==> i=i-1 (自减)
格式:
let varOPERvalue
实例:自增,自减
[root@arthur ~]# let var+=1
[root@arthur ~]# echo $var
1
[root@arthur ~]# let var++
[root@arthur ~]# echo $var
2
[root@arthur ~]# let var-=1
[root@arthur ~]# echo $var
1
[root@arthur ~]# let var--
[root@arthur ~]# echo $var
0
[root@arthur ~]#
[root@arthur ~]# unset i j ;i=1;let j=i++;echo "i=$i,j=$j"
i=2,j=1
[root@arthur ~]# unset i j ;i=1;let j=++i;echo "i=$i,j=$j"
i=2,j=2
[root@arthur ~]#
# i++ 与 ++i的区别:
i++ 先赋值再运算
++i 先运算再赋值
实例:鸡兔同笼问题
[root@arthur ~]# vim shell/chicken.sh
#!/bin/bash
HEAD=35
FOOT=94
RABBIT=$(((FOOT-HEAD-HEAD)/2))
CHOOK=$[35-RABBIT]
echo "兔子的数量为:"$RABBIT
echo "鸡的数量为: "$CHOOK
[root@arthur ~]# chmod +x shell/chicken.sh
[root@arthur ~]# shell/chicken.sh
兔子的数量为::12
鸡的数量为:23
[root@arthur ~]#
# 在脚本中写入变量,让用户在命令行写入需要计算的数值
[root@arthur ~]# vim shell/chicken.sh
#!/bin/bash
HEAD=$1
FOOT=$2
RABBIT=$(((FOOT-HEAD-HEAD)/2))
CHOOK=$[35-RABBIT]
echo "兔子的数量为:"$RABBIT
echo "鸡的数量为: "$CHOOK
[root@ansible-salve1 ~]# ./chicken.sh 30 80
兔子的数量为:10
鸡的数量为: 25
[root@ansible-salve1 ~]#
计算出所有人的年龄总和
[root@ansible-salve1 shell]# vim nianling.txt
[root@ansible-salve1 shell]# cat nianling.txt
a=20
b=18
c=22
[root@ansible-salve1 shell]# cut -d"=" -f2 nianling.txt
20
18
22
[root@ansible-salve1 shell]# cut -d"=" -f2 nianling.txt | tr '\n' + | grep -Eo ".*[0-9]" | bc
60
[root@ansible-salve1 shell]# grep -Eo "[0-9]+" nianling.txt
20
18
22
[root@ansible-salve1 shell]# grep -Eo "[0-9]+" nianling.txt | tr '\n' +| grep -Eo ".*[0-9]" | bc
60
[root@ansible-salve1 shell]#
True用数字表示1,False用数字表示0
与:&
1 与 1 = 1
1 与 0 = 0
0 与 1 = 0
0 与 0 = 0
或:|
1 或 1 = 1
1 或 0 = 1
0 或 1 = 1
0 或 0 = 0
非:!
!1 = 0 !True=False
!0 = 1 !False=True
异或:^
#异或的两个值,相同为假,不同为真
1 ^ 1 =0
1 ^ 0 =1
0 ^ 1 =1
0 ^ 0 =0
短路与
CMD1 短路与 CMD2
--第一个CMD1结果为0(假),总的结果必定为0,所以不需要执行CMD2
--第二个CMD1结果为1(真),第二个CMD2必须要参与计算,才能得到最终的结果
短路或
CMD1 短路或 CMD2
--第一个CMD1结果为1(真),总的结果必定为1,因此不需要执行CMD2
--第一个CMD1结果为0(假),第二个CMD2必须要参与运算,才能得到最终的结果
第一天的了解内容较多,直接记忆为主,总体来说难度较低。