1.shell不只是一种解释器,还是一种编程工具
查看系统中可用的shell,linux默认使用 Bash Shell
[root@localhost ~]# cat /etc/shells
/bin/sh
/bin/bash
/sbin/nologin
/usr/bin/sh
/usr/bin/bash
/usr/sbin/nologin
/bin/tcsh
/bin/csh
redhat系将/bin/sh连接到bash上
[root@localhost ~]# ll /bin/sh
lrwxrwxrwx. 1 root root 4 May 17 08:32 /bin/sh -> bash
[root@localhost ~]# bash --version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
2.shell的功能
当一台系统运行起来时,内核会被调入内存中运行,由内核执行所有底层的工作,它会将所有应用程序及用户的数据翻译为CPU的基本指令,并将其送至处理器。为了对用户屏蔽这些复杂的技术细节,同时也是为了保护内核不会因为用户直接操作而受到损害,有必要在内核之上创建一个层,该层就是一个"壳",也就是shell。
工作模式:互动模式和脚本模式
3.运行脚本
第一种:直接在该脚本所在目录中直接bash该脚本,脚本中的#!/bin/bash可以省略
第二种:给该脚本加上可执行权限,然后使用"./脚本名"来运行,没有权限会报错
第三种:把脚本的移动到$PATH的任意路径下,,使其成为默认的系统命令
4.脚本排错
加入echo或者echo结合sleep
借助-x参数来观察脚本的运行情况:bash -x helloworld.sh
为了更精细地调试运行shell,我们可以借助第三方工具bashdb。这是一个类似于GDB的脚本调试软件,小巧而强大,具有设置断点、单步执行、观察变量等功能。
5.shell的内建命令
就是bash自身提供的命令,而不是文件系统中的某个可执行文件。比如人类的语言沟通就是与生俱来的能力,而移动电话只是一个用来沟通的外部工具。
通常来说,内建命令会比外部命令执行得更快,执行外部命令时不但会触发磁盘I/O,还需要fork出一个单独的进程来执行,执行完后再退出。而执行内建命令相当于调用当前shell进程的一个函数。
判断一个命令是否为外部命令
cd是一个内建命令
[root@localhost ~]# type cd
cd is a shell builtin
ifconfig是一个外部命令
[root@localhost ~]# type ifconfig
ifconfig is /usr/sbin/ifconfig
6.执行程序
"."(点号)
点号用于执行某个脚本,甚至脚本没有可执行权限也可以运行,如果你不想修改某个测试脚本时,可以这样用。
[root@localhost ~]# ll helloworld.sh
-rw-r--r-- 1 root root 31 Jul 16 11:04 helloworld.sh
[root@localhost ~]# ./helloworld.sh
bash: ./helloworld.sh: Permission denied
没有权限也不会报错
[root@localhost ~]# . ./helloworld.sh
hello world
source与点号类似,source命令也可以读取并在当前环境中执行脚本,同时还可返回脚本中最后一个命令的返回状态;如果没有返回值则返回0,代表执行成功;如果未找到指定的脚本则返回false
7.别名
alias可用于创建命令的别名,若直接输入该命令且不带任何参数,则列出当前用户使用了别名的命令。
自定义别名,比如说一般的关机命令是shutdown -h now,写起来比较长,这时可以重新定义一个自己专属的关机命令
[root@localhost ~]# alias myshutdown='shutdown -h now'
不过这种方式在重新登陆之后会失效,如果想要用就生效,需要将该配置写在用户家目录中的.bashrc文件中。
[root@localhost ~]# less .bashrc
.bashrc
User specific aliases and functions
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
删除别名
unalias,该命令用于删除当前shell环境中的别名
第一种删除:unalias 某个命令的别名
第二种删除:unalias -a 这个删除当前shell环境中所有的别名
8.任务前后台切换:bg、fg、jobs
该命令用于将任务放置后台运行,一般会与ctrl+z、fg、&符号联合使用。典型的使用场景是运行比较耗时的任务。比如打包某个占用较大空间的目录,若在前台执行,那么在它完成之前会一直占用当前的终端,而导致无法执行其他任务,此时就应该将这类任务放置后台
ctrl+z:组合键用于暂停前台任务
jobs:查看暂停的任务
&:将任务放置后台运行,如果预知某个任务耗时很久,可以一开始就将命令放入后台运行
fg 1:将后台编号为1的任务放到当前运行
9.声明变量:declare、typeset
这两个命令的作用是用来生命变量的
declare -i声明整型变量
[root@localhost ~]# declare -i num1=1
declare -r 声明变量为只读
[root@localhost ~]# declare -r num2=2
[root@localhost ~]# num2=3
bash: num2: readonly variable
10.echo
默认情况下,echo会隐藏-e参数(禁止解释打印反斜杠转义的字符)。比如"
"代表换行,使用echo打印"
"时,只会把"
"当作普通字符处理。如果要打印转义字符,则需要通过使用-e参数来允许。
[root@localhost ~]# echo '
'
[root@localhost ~]# echo -e '
'
[root@localhost ~]#
11.跳出循环:break
从一个循环(for、while、until、select)中退出。break后可以跟一个数字n,代表跳出n层循环,n必须大于1,如果n比当前循环层数还要大,则跳出所有循环。
12.循环控制:continue
停止当前循环,并执行外层循环(for、while、until、select)的下一次循环。continue后可以跟一个数字n,代表跳至外部第n层循环,n必须大于1,如果n比当前循环层数还要大,则跳至最外层的循环。
13.eval
将所跟参数作为shell的输入,并执行产生的命令
用法例一:将字符串解析成命令执行
[root@localhost shell_learning]# cmd="ls -l /etc/passwd"
[root@localhost shell_learning]# eval $cmd
-rw-r--r-- 1 root root 2857 Jun 11 14:52 /etc/passwd
用法例二:程序运行中根据某个变量确定实际的变量名
[root@localhost shell_learning]# name1=john
[root@localhost shell_learning]# name2=wang
[root@localhost shell_learning]# num=1
[root@localhost shell_learning]# eval echo "$"name$num
john
用法例三:将某个变量的值当作另一个变量名并给其赋值
[root@localhost shell_learning]# name1=john
[root@localhost shell_learning]# name2=wang
[root@localhost shell_learning]# eval $name1="$name2"
[root@localhost shell_learning]# echo $john
wang
14.执行命令来取代当前的shell:exec
内建命令exec并不启动新的shell,而是用要被执行的命令替换当前的shell进程,并且将老进程的环境清理掉,而且exec命令后的其他命令将不再执行。假设在一个shell里面执行exec echo "hello"命令,那么打印hello之后终端就会断开(xshell)或者terminal直接退出(linux中的伪终端)。
为了避免上述情况,一般将exec命令放到一个shell脚本里面,由主脚本调用这个脚本,主脚本在调用子脚本执行时,当执行到exec后,该子脚本进程就被替换成相应的exec的命令。exec的典型用法是与find联合使用,用find找出符合匹配的文件,然后交给exec处理。
[root@localhost ~]# find / -name "*.conf" -exec ls -l {} ;
*{}与之间要有空格
15.退出shell:exit
在shell脚本中使用exit代表退出当前脚本,该命令可以接受的参数是一个状态值n,代表退出的状态,下面的脚本什么都不会做,一旦运行就以状态值为5退出。如果不指定,默认状态值是0。
[root@localhost ~]# vim exit.sh
[root@localhost ~]# sh exit.sh
[root@localhost ~]# echo $?
5
16.使变量能被子shell识别:export
用户登陆到系统之后,系统将启动一个shell,用户可以在该shell中声明变量,也可以创建并运行shell脚本。在父shell中创建的变量时,这些变量并不会被其他子shell进程所知,也就是说变量默认情况下是"私有"的,或称"局部变量"。使用export命令可以将变量导出,使得该shell的子shell都可以使用该变量,这个过程称为变量输出。
[root@localhost ~]# cat export.sh
!/bin/bash
echo $var
[root@localhost ~]# var=100
[root@localhost ~]# echo $var
100
[root@localhost ~]# sh export.sh
export之后,子shell就能访问父shell里面$var的值了
[root@localhost ~]# export var=100
[root@localhost ~]# echo $var
100
[root@localhost ~]# sh export.sh
100
需要注意的一点,子shell读取到父shell中变量var的值,但这只是值的传递。如果在子shell中尝试修改变量var的值,改变的只是var在子shell中的值,并不会影响父shell中var的值
17.整数运算
[root@localhost ~]# let i=2+2
[root@localhost ~]# echo $i
4
求餘
[root@localhost ~]# let M=15%7
[root@localhost ~]# echo $M
1
次方
[root@localhost ~]# let N=2**3
[root@localhost ~]# echo $N
8
自加一
[root@localhost ~]# i=1
[root@localhost ~]# let i++
[root@localhost ~]# echo $i
2
自加十
[root@localhost ~]# echo $i
2
[root@localhost ~]# let i+=10
[root@localhost ~]# echo $i
12
自乘十
[root@localhost ~]# let i*=2
[root@localhost ~]# echo $i
24
自除六
[root@localhost ~]# let i/=6
[root@localhost ~]# echo $i
4
18.聲明局部變量:local
用於在腳本內定義局部變量,典型的用法是用於函數體內,其作用域也在聲明該變量的函數體中。如果試圖在函數外使用local聲明變量,則會提示錯誤
19.从标准输入读取一行到变量:read
有时候我们开发的脚本必须具有交互性,也就是在运行过程依赖人工输入才能继续。
[root@localhost shell_learning]# cat read.sh
!/bin/bash
declare N
echo "12 bottles of beer in a box"
echo -n "How many box do you want:"
read N
echo "$((N*12)) bottle in total"
[root@localhost shell_learning]# sh read.sh
12 bottles of beer in a box
How many box do you want3
36bottle in total
优化
!/bin/bash
echo "12 bottles of beer in a box"
read -p "How many box do you want:" N
echo "$((N*12)) bottle in total"
20.定义函数返回值:return
常见的用法是返回一个数字return n,如果没有指定n值,则返回状态是函数体中执行的最后一个命令的退出状态
[root@localhost ~]# cat return.sh
!/bin/bash
function fun_01 {
return 1
}
fun_01
echo $?
[root@localhost ~]# bash return.sh
1
21.向左移动位置参数:shift
一个脚本在运行时可以接收,从左到有第一个参数被记作$1,第二个参数为$2,以此类推。所有参数记作$@或$*
参数总个数记为$#,脚本本身记作$0
shift命令可以对脚本的参数做"偏移"操作。假设脚本有A B C这三个参数,那么$1为A,$2为B,$3为C;shift一次
之后$1为B,$2为C。再shift一次,$1为C
22.显示并设置进程资源限度:ulimit
软限制不能高于硬限制
查看系统软限制
[root@localhost ~]# ulimit -a
core file size (blocks, -c) 0 文件大小
data seg size (kbytes, -d) unlimited 数据段大小
scheduling priority (-e) 0 调度优先级
file size (blocks, -f) unlimited 创建文件的大小
pending signals (-i) 63121 挂起的信号数量
max locked memory (kbytes, -l) 64 最大锁定内存的值
max memory size (kbytes, -m) unlimited 最大可用的常驻内存值
open files (-n) 1024 最大的文件打开数
pipe size (512 bytes, -p) 8 管道最大缓冲区的值
POSIX message queues (bytes, -q) 819200 消息队列的最大值
real-time priority (-r) 0 程序的实时性优先级
stack size (kbytes, -s) 8192 栈大小
cpu time (seconds, -t) unlimited 最大cpu占用时间
max user processes (-u) 63121 用户最大进程数
virtual memory (kbytes, -v) unlimited 最大虚拟内存
file locks (-x) unlimited 文件锁
设置最大文件打开数(命令行设置,在系统重启后就失效)
同时设置软限制和硬限制
[root@localhost ~]# ulimit -n 4096
仅设置软连接
[root@localhost ~]# ulimit -S -n 4096
仅设置硬连接
[root@localhost ~]# ulimit -H -n 4096
修改配置文件(永久生效)
格式:
可用item
- can be one of the following:
- core - limits the core file size (KB)
- data - max data size (KB)
- fsize - maximum filesize (KB)
- memlock - max locked-in-memory address space (KB)
- nofile - max number of open file descriptors 最大文件打开数
- rss - max resident set size (KB) 最大常驻内存值
- stack - max stack size (KB)
- cpu - max CPU time (MIN)
- nproc - max number of processes 最大进程数
- as - address space limit (KB) 虚拟地址空间
- maxlogins - max number of logins for this user
- maxsyslogins - max number of logins on the system
- priority - the priority to run user process with
- locks - max number of file locks the user can hold
- sigpending - max number of pending signals
- msgqueue - max memory used by POSIX message queues (bytes)
- nice - max nice priority allowed to raise to values: [-20, 19]
- rtprio - max realtime priority
23.测试表达式:test
根据测试结果返回0(测试失败)或1(测试成功)
查看系统中可用的shell
[root@localhost ~]# cat /etc/shells
/bin/sh
/bin/bash
/sbin/nologin
/usr/bin/sh
/usr/bin/bash
/usr/sbin/nologin
/bin/tcsh
/bin/csh
修改用户登陆的shell
chsh 用户名
[root@localhost ~]# chsh liangjiongyao
Changing shell for liangjiongyao.
New shell [/bin/bash]: /bin/sh
Shell changed.
不加用户名默认就是root
其实修改的是/etc/passwd里面的最后一列
liangjiongyao❌1000:1000:liangjiongyao:/home/liangjiongyao:/bin/sh
#########
变量赋值和取值
注意点:变量名和变量值之间用等号紧紧相连,之间没有任何空格--skip-broken
[root@localhost ~]# name=john
[root@localhost ~]# echo $name
john
[root@localhost ~]# name="joe"
[root@localhost ~]# echo $name
joe
当变量有空格时必须用引号括起,否则会报错
[root@localhost ~]# name=john wang
bash: wang: command not found...
[root@localhost ~]# name="john wang"
[root@localhost ~]# echo $name
john wang
注意点:获取变量值的一种比较保险的方式
[root@localhost ~]# name="john wang"
[root@localhost ~]# echo ${name}
john wang
[root@localhost ~]# name="sue"
[root@localhost ~]# echo $namehello #变量namehello没有定义
[root@localhost ~]# echo ${name}hello
suehello
注意点:如果变量值引用的是其他变量,则必须使用双引号。因为单引号会阻止shell解释变量
[root@localhost ~]# name="john"
[root@localhost ~]# name1="$name"
[root@localhost ~]# echo $name1
john
[root@localhost ~]# name2='$name'
[root@localhost ~]# echo $name2
$name
因为shell具有弱变量的特性,因此即便在没有预先声明变量的好时候也是可以引用的。如果在脚本中引用没有定义的
变量,导致脚本异常,那么排查起来也很困难。因此必须使脚本遵循"先声明再使用"。
[root@localhost ~]# echo $undefinedVar
[root@localhost ~]# shopt -o -s nounset
[root@localhost ~]# echo $undefinedVar
bash: undefinedVar: unbound variable
取消变量
就是将变量从内存中释放,使用的命令是unset,后面跟变量名,函数也是可以被取消的。
[root@localhost ~]# name=john
[root@localhost ~]# echo $name
john
[root@localhost ~]# unset name
[root@localhost ~]# echo $name
bash: name: unbound variable
取消函数
[root@localhost liangjiongyao]# cat test
!/bin/bash
unset_function(){
echo "Hello World"
}
unset unset_function
unset_function
[root@localhost liangjiongyao]# bash test
test: line 7: unset_function: command not found
命令的返回值
linux中规定正常退出的命令和脚本应该以0作为其返回值,任何非0的返回值都表示命令未正确退出或未正常执行
[root@localhost liangjiongyao]# ifco
bash: ifco: command not found...
[root@localhost liangjiongyao]# echo $?
127
[root@localhost liangjiongyao]# ping 1
connect: Invalid argument
[root@localhost liangjiongyao]# echo $?
2
数组
数组是一种特殊的数据结构,其中的每一项被称为一个元素,对于每个元素,都可以用索引方式取出元素的值。shell中数组对元组个数没有限制,
但只支持一维数组。
定义数组
[root@localhost liangjiongyao]# declare -a Array
[root@localhost liangjiongyao]# Array[0]=0
[root@localhost liangjiongyao]# Array[1]=1
[root@localhost liangjiongyao]# Array[2]="HelloWorld"
创建时赋值
[root@localhost liangjiongyao]# declare -a Name=('john''sue')
不使用declare关键字创建数组
[root@localhost liangjiongyao]# Name=('john''sue')
对特定的元素赋值
[root@localhost liangjiongyao]# Score=([3]=3 5=[5] [7]=7)
数组操作
取值:${数组名[索引]}
[root@localhost liangjiongyao]# echo ${Array[0]}
0
[root@localhost liangjiongyao]# echo ${Array[2]}
HelloWorld
[root@localhost liangjiongyao]# echo ${Array[1]}
1
一次性取出数组中所有元素的值
[root@localhost liangjiongyao]# echo ${Array[@]}
0 1 HelloWorld #以空格隔开的元素值
[root@localhost liangjiongyao]# echo ${Array[*]}
0 1 HelloWorld #一整个字符串
获取数组的长度(即元素个数)
[root@localhost liangjiongyao]# echo ${#Array[*]}
3
[root@localhost liangjiongyao]# echo ${#Array[@]}
3
如果某个元素是字符串,还可以通过指定索引的方式获得该元素的长度
[root@localhost liangjiongyao]# echo ${#Array[2]}
10
[root@localhost liangjiongyao]# echo ${Array[2]}
HelloWorld
取出数组的第一、第二个元素
[root@localhost liangjiongyao]# echo ${Array[@]:1:2}
1 HelloWorld
取出数组的第二个元素中的第0~5个字符
[root@localhost liangjiongyao]# echo ${Array[2]:0:5}
Hello
连接数组:将若干个数组进行拼接操作
[root@localhost liangjiongyao]# Conn=(${Array[@]} ${Name[@]})
[root@localhost liangjiongyao]# echo ${Conn[@]}
0 1 HelloWorld johnsue
替换元素:将数组内某个元素的值替换成其他值
[root@localhost liangjiongyao]# Array=(${Array[@]/HelloWorld/HelloJohn})
[root@localhost liangjiongyao]# echo ${Array[@]}
0 1 HelloJohn
取消数组或元素
取消数组中的一个元素
[root@localhost liangjiongyao]# unset Array[1]
[root@localhost liangjiongyao]# echo ${Array[@]}
0 HelloJohn
取消整个数组
[root@localhost liangjiongyao]# unset Array
[root@localhost liangjiongyao]# echo ${Array[@]}
本行为空
只读变量
通过readonly内建命令创建的变量
[root@localhost liangjiongyao]# readonly ro=100
[root@localhost liangjiongyao]# ro=200
bash: ro: readonly variable
[root@localhost liangjiongyao]# declare -r rp=100
[root@localhost liangjiongyao]# rp=200
bash: rp: readonly variable
变量的作用域
又称“名称空间”,表示变量的上下文。相同的变量可以在多个命名空间中定义,并且彼此之间互不相干。
就像A班有个小明,B班也有个小明,虽然他们都叫小明但是由于所在的班级不一样(对应于命名空间),所以不会造成混乱
2018-08-23 晚
控制字符
即ctrl+key组合键一起使用,用于修改终端或文本显示,一般在命令行下使用,脚本中不能使用。
ctrl+L 清屏
ctrl+Z 暂停前台作业
ctrl+U 删除光标到行首的所有字符
测试结构
格式一:test expression是一个表达式,可以是算术比较、字符串比较、文本和文件属性比较等
格式二:使用[ expression ] #推荐,易于与if、case、while这些条件判断的关键字连用
[root@localhost yum.repos.d]# test -e /var/log/messages
[root@localhost yum.repos.d]# echo $?
0
[root@localhost yum.repos.d]# test -e /var/log/messages01
[root@localhost yum.repos.d]# echo $?
1
[root@localhost yum.repos.d]# [ -e /var/log/messages ]
[root@localhost yum.repos.d]# echo $?
0
[root@localhost yum.repos.d]# [ -e /var/log/messages01 ]
[root@localhost yum.repos.d]# echo $?
1
文件测试符
-b 当文件存在且是个块文件时返回真,否则为假
-c 当文件存在且是个字符设备时返回真,否则为假
-d 当文件存在且是个目录时返回真,否则为假
-e 当文件或者目录存在时返回真,否则为假
-f 当文件存在且为普通文件时返回真,否则为假
-x 当文件存在且为可执行文件时返回真,否则为假
-s 当文件存在且大小不为0时返回真,否则为假
-S 当文件存在且为socket文件时返回真,否则为假
-r 当文件存在且为可读文件时返回真,否则为假
-w 当文件存在且为可写文件时返回真,否则为假
-x 当文件存在且为可执行文件时返回真,否则为假
FILE1 -nt FILE2 当FILE1比FILE2新时返回真,否则为假
FILE1 -ot FILE2 当FILE1比FILE2旧时返回真,否则为假
文件新旧的比较的主要使用场景是判断文件是否被更新或增量备份时,用于判断一段时间以来更新过的文件
字符串测试
-z "string" 字符串string为空时返回真,否则为假
-n "string" 字符串string非空时返回真,否则为假
"string1" = "string1" string1和string2相同时返回真,否则为假
"string1" != "string1" string1和string2不相同时返回真,否则为假
"string1" > "string2" 按照字典排序,字符串string1排在string2之前返回真,否则为假
"string1" < "string2" 按照字典排序,字符串string1排在string2之后返回真,否则为假
[root@localhost yum.repos.d]# str1=""
[root@localhost yum.repos.d]# str2="hello"
[root@localhost yum.repos.d]# [ "$str1" > "$str2" ] #大于号需要转义
[root@localhost yum.repos.d]# echo $?
1
[root@localhost yum.repos.d]# [[ "$str1" > "$str2" ]] #不向转义就用[[]]
[root@localhost yum.repos.d]# echo $?
1
整数测试
-eq 等于
-gt 大于
-lt 小于
-ge 大于等于
-le 小于等于
-ne 不等于
[root@localhost yum.repos.d]# num1=10
[root@localhost yum.repos.d]# num2=10
[root@localhost yum.repos.d]# num3=9
[root@localhost yum.repos.d]# num4=11
[root@localhost yum.repos.d]# [ "$num1" -eq "$num2" ]
[root@localhost yum.repos.d]# echo $?
0
[root@localhost yum.repos.d]# [ "$num1" -gt "$num3" ]
[root@localhost yum.repos.d]# echo $?
0
[root@localhost yum.repos.d]# [ "$num1" -ge "$num2" ]
[root@localhost yum.repos.d]# echo $?
0
[root@localhost yum.repos.d]# [ "$num1" -le "$num2" ]
[root@localhost yum.repos.d]# echo $?
0
[root@localhost yum.repos.d]# [ "$num1" -ne "$num3" ]
[root@localhost yum.repos.d]# echo $?
0
逻辑测试符和逻辑运算符
逻辑测试用于连接多个测试条件,并返回整个表达式的值
逻辑非的使用
测试值为真的表达式在使用逻辑非后,表达式变为假,反之亦然
[root@localhost yum.repos.d]# [ ! -e /var/log/messages ]
[root@localhost yum.repos.d]# echo $?
1
逻辑与使用
表达式都为真,整个表达式才返回真,否则返回假
[root@localhost yum.repos.d]# [ -e /var/log/messages -a -e /var/log/messages ]
[root@localhost yum.repos.d]# echo $?
0
[root@localhost yum.repos.d]# [ -e /var/log/messages -a -e /var/log/messages01 ]
[root@localhost yum.repos.d]# echo $?
1
逻辑或的使用
测试表达式中只要有真,则整个表达式返回真
[root@localhost yum.repos.d]# [ -e /var/log/messages -o -e /var/log/messages01 ]
[root@localhost yum.repos.d]# echo $?
0
逻辑运算符
! 非
&& 与
|| 或
[root@localhost yum.repos.d]# ! [ -e /var/log/messages ]
[root@localhost yum.repos.d]# echo $?
1
[root@localhost yum.repos.d]# [ -e /var/log/messages ]&&[ -e /var/log/messages01 ]
[root@localhost yum.repos.d]# echo $?
1
[root@localhost yum.repos.d]# [ -e /var/log/messages ]||[ -e /var/log/messages01 ]
[root@localhost yum.repos.d]# echo $?
0
判断
if判断结构
if exp;then
command
......
fi
[root@localhost liangjiongyao]# cat score01.sh
!/bin/bash
echo -n "please input a score:"
read score
if [ "$score" -lt 60 ];then
echo "C"
fi
if [ "$score" -lt 80 -a "$score" -ge 60 ];then
echo "B"
fi
if [ "$score" -ge 80 ];then
echo "A"
fi
分支(if/else)
if exp;then
command
else
command
fi
[root@localhost liangjiongyao]# cat check_file.sh
!/bin/bash
FILE=/var/log/messages
if [ -e $FILE ];then
echo "$FILE exist"
else
echo "$FILE not exist"
fi
if/elif/else判断结构
if exp1;then
command
elif exp2;then
command
else
command
fi
优化score01.sh
[root@localhost liangjiongyao]# cat score02.sh
!/bin/bash
echo -n "please input a score:"
read score
if [ "$score" -lt 60 ];then
echo "C"
elif [ "$score" -lt 80 -a "$score" -ge 60 ];then
echo "B"
else
echo "A"
fi
case判断结构
与if/elif/else一样,可用于多种情况下的分支选择,语法如下:
case VAR in
var1)command1;;
var2)command1;;
var3)command1;;
var4)command1;;
...
esac
原理为从上到下依次比较VAR和var1、var2、var3的值是否相等,如果匹配相等则执行后面的命令语句,在无一匹配的情况下
匹配最后默认的*,并执行后面的默认命令。需要注意的是,var1 var2 var3等这些值只能是常量或正则表达式
[root@localhost liangjiongyao]# cat os_type.sh
!/bin/bash
OS=uname -s
case "$OS" in
FreeBSD) echo "this is Cygwin";;
SunOS) echo "this is Solaris";;
AIX) echo "this is Minix";;
Linux) echo "this is Linux";;
*) echo "Failed to identify this OS";;
esac
2018-08-25
带参数的函数
位置参数
[root@localhost liangjiongyao]# cat checkFileExist_v2.sh
!/bin/bash
function checkFileExist(){
if [ -f $1 ];then
return 0
else
return 1
fi
}
checkFileExist $1
if [ $? -eq 0 ];then
echo "$1 exist"
else
echo "$1 not exist"
fi
使用的时候直接向脚本传递文件全路经的方式传递参数
[root@localhost liangjiongyao]# bash checkFileExist_v2.sh /etc/notExist
/etc/notExist not exist
[root@localhost liangjiongyao]# bash checkFileExist_v2.sh /etc/passwd
/etc/passwd exist
移动位置参数
前面有提到,shift命令可让位置参数左移一位
[root@localhost liangjiongyao]# cat shift_03.sh
!/bin/bash
until [ $# -eq 0 ]
do
echo "Now $1 is: $1,total parameter is:$#"
shift
done
[root@localhost liangjiongyao]# bash shift_03.sh a b c d
Now $1 is: a,total parameter is:4
Now $1 is: b,total parameter is:3
Now $1 is: c,total parameter is:2
Now $1 is: d,total parameter is:1
如果在shift加数字n,则位置参数将会每次移动n位
下面利用shift计算脚本中所有参数的和
[root@localhost liangjiongyao]# cat shift_04.sh
!/bin/bash
tatal=0
until [ $# -eq 0 ]
do
let "tatol=tatol + $1"
shift
done
echo $tatol
[root@localhost liangjiongyao]# bash shift_04.sh 1 2 3 4
10
函数库
对于某些常用的功能,必须考虑将其独立出来,集中存放在一些独立的文件中,这些文件就被称为“函数库”。
函数库的本质也是“函数”,所以它的定义方式和普通函数没有任何区别,但为了和一般函数区分开来,在实践中建议函数库
使用下划线开头。
自定义函数库
[root@localhost liangjiongyao]# cat lib01.sh
!/bin/bash
_checkFileExist(){
if [ -f $1 ];then
echo "$1 exist"
else
echo "$1 not exist"
fi
}
其他脚本在希望直接调用_checkFileExist函数时,可以通过直接加载lib01.sh函数库的方式实现。加载方式有如下两种:
使用“点”命令
[root@localhost liangjiongyao]# ./PATH/TO/LIB
使用source命令
[root@localhost liangjiongyao]# source /PATH/TO/LIB
假设现在有个脚本想要直接调用_checkFileExist函数,可以通过加载lib01.sh函数库来实现。
[root@localhost liangjiongyao]# cat lib01.sh
!/bin/bash
_checkFileExist(){
if [ -f $1 ];then
echo "$1 exist"
else
echo "$1 not exist"
fi
}
[root@localhost liangjiongyao]# cat callLib01.sh
!/bin/bash
source ./lib01.sh #当前路径下
_checkFileExist /etc/notExistFile
_checkFileExist /etc/passwd
[root@localhost liangjiongyao]# bash callLib01.sh
/etc/notExistFile not exist
/etc/passwd exist
function函数库
位置:/etc/init.d/functions
function函数库中的常用函数
checkpid() 检查某个PID是否存在
daemon() 以daemon方式启动某个服务
killproc() 停止某个进程
pidfileofproc() 检查某个进程的PID文件
pidofproc() 检查某个进程的PID
status() 判断某个服务的状态
echo_success() 打印OK
echo_failure() 打印FAILED
echo_passed() 打印PASSED
echo_warning() 打印Waring
success() 打印OK并记录日志
failure() 打印FAILED并记录日志
passed() 打印PASSED并记录日志
warning() 打印Waring并记录日志
action() 执行给定的命令,并根据执行结果打印信息
strstr() 检查$1字符串中是否含有$2字符串
comfirm() 提示是否启动某个服务
递归函数
在函数体中继续调用函数自身,但是递归函数不可能无限递归下去,所以它一定要有结束递归的条件,当满足该条件时,递归就会终止。
结构如下:
function recursion(){
recursion
conditionThatEndTheRecursion #停止递归的条件
}
使用-x可以观察脚本运行过程
重定向
所谓“重定向”,就是将原本应该从标准输入设备(键盘)输入的数据,改由其他文件或设备输入;或将原本应该输出到标准输出设备(显示器)的内容,
改而输出到其他文件或设备上。
文件标识符和标准输入输出
文件标识符是重定向中一个很重要的一个概念,linux使用0到9的整数指明了与特定进程相关的数据流,系统在启动一个进程的同时会为该进程打开上个文件:
标准输入(stdin)、标准输出(stdout)、标准错误输出(stderr),分别用文件标识符0、1、2来标识。如果要为进程打开其他的输入输出,则需要从整数3开始标识。
默认情况下,标准输入为键盘,标准输出和错误输出为显示器。
I/O重定向
I/O重定向可以将任何文件、命令、脚本、程序或脚本的输出重定向到另外一个文件、命令、程序或脚本
标准输出覆盖重定向,默认将文件标识符为1的内容重定向到指定文件中
标准输出追加重定向
& 标准输出重定向
< 标准输入重定向
| 管道
如果命令由于各种原因出错时所产生的错误输出,其文件标识符为2,可以将其重定向到文件中就不会出现在显示器上了
[root@localhost liangjiongyao]# ls -l /usr/noexc 2> 1.txt
[root@localhost liangjiongyao]# cat 1.txt
ls: cannot access /usr/noexc: No such file or directory
标识输出重定向:>&
将标准输出和标准错误同时定向到同一个文件中
[root@localhost liangjiongyao]# command > stdout_stderr.txt1 2>&1
将错误输出重定向到“宇宙黑洞”
[root@localhost liangjiongyao]# command > stdout_stderr.txt2 2>/dev/null
标准输入重定向:<
从文件读取内容输入,也就是将文件内容写入标准输入中
[root@localhost ~]# cat < helloworld.txt
hello
world
[root@localhost ~]# cat helloworld.txt
hello
world
排序
[root@localhost ~]# cat fruit01.txt
banana
carrot
apple
[root@localhost ~]# sort < fruit01.txt
apple
banana
carrot
2018-08-26 晚
for循环在按行读取文件内容时,任何空白字符都可以作为其读取的分隔符;而while的按行读取就没有这个问题,因为while是使用换行符
作为行标记的
用cut截取第一列, -f1是第一列的意思,-d是指定分隔符
[root@localhost ~]# cat user_info.txt | cut -f1 -d' '
user001
user002
user003
非交互式给用户创建密码
echo "password" | passwd --stdin xpp
ftp服务端配置文件 /etc/vsftpd/vsftpd.conf中
anon_upload_enable=YES #允许匿名用户上传文件
anon_other_write_enable=YES #允许匿名用户覆盖同名文件
利用md5sum校验文件是否被修改
[root@localhost ~]# md5sum /etc/passwd > passwd.md5
[root@localhost ~]# md5sum -c passwd.md5
/etc/passwd: OK #如果没有被修改,则打印OK
[root@localhost ~]# useradd xpp
[root@localhost ~]# md5sum -c passwd.md5
/etc/passwd: FAILED
md5sum: WARNING: 1 computed checksum did NOT match
[root@localhost ~]#
利用ssh远程执行命令
[root@localhost ~]# ssh root@192.168.3.2 "echo $HOSTNAME"
利用脚本自动执行ssh-copy-id
[root@localhost ~]# cat expect_ssh_copy_id.sh
!/bin/bash
PASS=$1
IP=$2
auto_ssh_copy_id() {
expect -c "set timeout -1;
spawn /usr/bin/ssh-copy-id -i /root/.ssh/id_rsa.pub root@$2;
expect {
(yes/no) {send -- yes
;exp_continue;}
Password: {send -- $1
;exp_continue;}
eof {exit 0;}
}";
}
#######2018-08-27晚
iptables防火墙
4表:filter表(用于过滤)、nat表(地址或端口映射)、mangle表(对特定数据包的修改)、raw表
5链:prerouting 数据包进入路由决策之前
input 路由决策为本机的数据包
forward 路由决策不是本机的数据包
output 由本机产生的向外发送的数据包
postrouting 发送给网卡之前的数据包
[root@localhost ~]# ssh root@ceph-deploy
The authenticity of host 'ceph-deploy (192.168.101.2)' can't be established.
ECDSA key fingerprint is SHA256:UKNbnhKXfY/fODVDTQMcFyubYQdC0CqXuPf7hTzreHo.
ECDSA key fingerprint is MD5:e0:a9:1b:7c:2a:f0:0c:fc:01:7c:6b:7f:27:7a:5d:58.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'ceph-deploy' (ECDSA) to the list of known hosts.
Last login: Wed Aug 15 12:23:15 2018 from 192.168.122.1
[root@ceph-deploy ~]# ping www.baidu.com
PING www.a.shifen.com (183.232.231.173) 56(84) bytes of data.
64 bytes from 183.232.231.173 (183.232.231.173): icmp_seq=1 ttl=52 time=6.81 ms
清空所有的规则
[root@ceph-deploy ~]# iptables -F
删除所有自定义的链
[root@ceph-deploy ~]# iptables -X
丢弃进来的数据包
[root@ceph-deploy ~]# iptables -P INPUT DROP
[root@localhost ~]# ssh root@ceph-deploy
^C ###连不上了
丢弃出去的数据包
[root@ceph-deploy ~]# iptables -P OUTPUT DROP
[root@ceph-deploy ~]# ping www.baidu.com
...... ###ping不通了
[root@ceph-deploy ~]# ping 127.0.0.1
ping:sendmsg: Operation not permitted
......
目前的状况跟拔掉网线的操作没什么不同,而且比没有防火墙更糟糕的是本地数据包都无法通信了。因此需要一些策略来保证一些基本功能可用,所以要设置
以下规则。
允许icmp包进入
iptables -A INPUT -p icmp --icmp-type any -j ACCEPT
允许icmp出去
iptables -A OUTPUT -p icmp --icmp-type any -j ACCEPT
[root@ceph-deploy ~]# ping www.baidu.com
...... ###仍不通
[root@ceph-deploy ~]# ping 127.0.0.1
64 bytes from 127.0.0.1......
[root@ceph-deploy ~]# ping 192.168.122.1 #网关
64 bytes from 192.168.122.1......
[root@localhost ~]# ping 192.168.122.2 #网关能ping该主机
PING 192.168.122.2 (192.168.122.2) 56(84) bytes of data.
64 bytes from 192.168.122.2: icmp_seq=1 ttl=64 time=0.267 ms
[root@localhost ~]# ssh root@192.168.122.2
^C #依旧ssh不上
允许本地数据包出
iptables -A OUTPUT -s localhost -d localhost -j ACCEPT
允许本地数据包进
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
允许以建立和相关的数据包进入
iptables -A OUTPUT -M state --state ESTABLISHED,RELATED -j ACCEPT
如果是一台web服务器,典型的需要是能访问80端口,但是就目前的策略而言是无法访问的,所以需要允许80端口访问
iptables -A INPUT -p tcp --dport 80 -j ACCEPT #外来数据包可以通过本地的80端口进来
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT #保证发给对端的数据包包能通过本地80端口出去
这里使用状态跟踪模块,意思是对能建立完整的连接以及为了维持该连接需要打开的其他连接所产生的数据包都是可以通过防火墙的OUTPUT链。
如果需要允许当前服务器访问其他web服务器,该怎么办?只要打开让数据出去的80端口就可以了。
iptables -A OUTPUT -p tcp -m state --state NEW --dport 80 -j ACCEPT
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
如果需要访问www.baidu.com,就必须要允许域名解释的数据包出去,因为服务器访问该域名之前需要先解释出它的IP地址
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
由于管理需要ssh到这台服务器,则需要打开22端口
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
如果只能允许一个固定的IP能ssh到该服务器的话,上面的语句需要该为
iptables -A INPUT -p tcp --dport 22 -s 192.168.1.10 -j ACCEPT
可能还需要从该服务器ssh到别的服务器
iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT
自定义开机启动项的init脚本
init脚本是linux系统用于启动系统服务的脚本,存放路径为/etc/init.d/。系统在启动时将根据当前的运行等级确定
运行在/etc/rc.d/rcX.d目录下的脚本(都是到/etc/init.d目录中的文件软连接)
init脚本必须接收至少两个参数:start和stop,用于启动和停止服务。建议在写init脚本时,将各种参数的执行体封装成函数的格式。
使用脚本操作MySQL数据库
查看本地所有数据库
[root@localhost ~]# mysql -uroot -p123 -e "show databases"
使用脚本
[root@localhost ~]# cat mysql01.sh
!/bin/bash
username="root"
password="123"
cmd="show databases"
mysql -u$username -p$password -e "$cmd"
[root@localhost ~]# bash mysql01.sh
+--------------------+
| Database |
+--------------------+
| information_schema |
| Django_ORM |
| LJY |
| ORM_mutil |
| char_set_test |
| django_demo01 |
| liangjiongyao |
| ljy |
| mysql |
| performance_schema |
| test |
+--------------------+
创建数据库
create_db_sql="create database ${database}"
mysql -u$username -p$password -e "$create_db_sql"
创建表
create_table_sql="create table ${table}(name varchar(20),id int(10))"
mysql -u$username -p$password $dbname -e "$create_table_sql"
插入数据
insert_sql="insert into ${table} values('john',1)"
mysql -u$username -p$password $dbname -e "$insert_sql"
查询
select_sql="select * from $table"
mysql -u$username -p$password $dbname -e "$select_sql"
更新数据
update_sql="upadte $table set id=3"
mysql -u$username -p$password $dbname -e "$update_sql"
删除数据
delete_sql="delete from ${table}"
mysql -u$username -p$password $dbname -e "$delete_sql"
使用here Document执行SQL代码块
[root@localhost ~]# cat mysql02.sh
!/bin/bash
mysql -uroot -p123 << EOF
create database db01;
use db01;
create table user01(
userid int(20) not null,
username varchar(20) not null,
userpass varchar(20) not null,
age int(10) not null,
primary key(userid)
);
EOF
[[root@localhost ~]# bash mysql02.sh root@localhost ~]# bash mysql02.sh
[root@localhost ~]# bash mysql02.sh
[root@localhost ~]# mysql -uroot -p123 -e "show databases;"
+--------------------+
| Database |
+--------------------+
| information_schema |
| Django_ORM |
| LJY |
| ORM_mutil |
| char_set_test |
| db01 |
| django_demo01 |
| liangjiongyao |
| ljy |
| mysql |
| performance_schema |
| test |
+--------------------+
[root@localhost ~]# mysql -uroot -p123 db01 -e "show tables;"
+----------------+
| Tables_in_db01 |
+----------------+
| user01 |
+----------------+
[root@localhost ~]# mysql -uroot -p123 db01 -e "select * from user01;"
[root@localhost ~]# mysql -uroot -p123 db01 -e "desc user01;"
+----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| userid | int(20) | NO | PRI | NULL | |
| username | varchar(20) | NO | | NULL | |
| userpass | varchar(20) | NO | | NULL | |
| age | int(10) | NO | | NULL | |
+----------+-------------+------+-----+---------+-------+
[root@localhost ~]# cat insert.sql | mysql -uroot -p123 db01
[root@localhost ~]# mysql -uroot -p123 db01 -e "select * from user01;"
+--------+----------+----------+-----+
| userid | username | userpass | age |
+--------+----------+----------+-----+
| 1 | ljy | 123 | 23 |
+--------+----------+----------+-----+
[root@localhost ~]# cat insert.sql
insert into user01 values(1,'ljy',123,23)
使用lvm备份mysql数据
使用mysqldump或mysqlhotcopy进行备份,这种方式往往会造成数据备份不一致的问题。在备份的过程中,当备份工具完成了
备份表A的备份之后,在备份表B时,可能A表记录已经发生了变化,这样的备份文件实际上是无法用于数据库恢复。解决这个问题的方法是在整个备份过程中锁定表,
直至备份结束,但这会影响网站的可用性,因为在数据库备份过程中又与数据库长时间被锁定而无法写入任何数据
lvm的快照功能可以很好地解决这个问题,在对一个lv创建snapshot时,仅会复制其中的元数据,而不回有任何真实数据的复制,所以创建过程几乎是实时的。
当原有的LV有写操作时,数据会写到快照中而不是原LV中(cow),从而保证了原LV中数据的一致性。为了确保数据一致性,在对其做快照之前也需要对数据库进行锁定操作,
做完快照后再解除锁。又与快照的过程极为迅速,所以短时间的数据库锁定并不会对前台应用造成影响。
注意:快照创建的大小必须考虑备份期间数据的变化量,如果数据变化量大于快照的大小则会造成快照损坏,所以在对特别重要的数据进行快照备份时,要让快照的大小必须至少等于原LV的大小
使用lvm的快照的前提是,mysql数据库的数据文件必须存储在LV上,数据目录使用逻辑卷。
创建PV
pvcreate mysql_pv /dev/sdb
创建VG
vgcreate mysql_vg /dev/sdb
创建LV
lvcreate -L 1024M -n mysql_lv mysql_vg
格式话挂载
mkfs.ext4 /dev/mysql_vg/mysql_lv
挂载lv
mount /dev/mysql_vg/mysql_lv /mount
关闭mysql服务,将/var/lib/mysql/中的数据全部复制到新创建的LV中
systemctl stop mysqld
cp -a /var/lib/mysql/* /mnt
将/dev/mysql_vg/mysql_lv重新mount,并启动mysql
umount /dev/mysql_vg/mysql_lv
mount /dev/mysql_vg/mysql_lv /var/lib/mysql/
systemctl start mysqld
最后不要忘记写/etc/fatab文件,开机自动挂载该LV到/var/lib/mysql/
在对mysql_lv做快照之前,先使用flush tables和flush tables eith read lock强行将所有OS的缓冲数据写入磁盘(类似于操作系统的sync),同时将数据库设置为全局只读。
快照完成之后,再使用unlock tables解锁。然后只需要将快照进行挂载,复制其中的数据,在复制完成后删除该快照即可。