大纲目录:
Chapter 1: 初级任务,主要是掌握Bash中的基本概念与特性。
Chapter 2: 常见重要命令
Chapter 3: 文件相关
Chapter 4: 文本处理
Chapter 5: 网络相关
Chapter 6: 数据备份,归档,压缩
Chapter 7: 联网实践
Chapter 8: 系统活动监控的常见方式
Chapter 9: 系统管理的实战
------------------------------------------------------------------------------------------
$表示普通用户,#表示超级用户(root user)
#! /bin/bash --
#!被称为Shebang。Shebang这一语法特性由#!开头,即井号和叹号。 在开头字符之后,可以有一个或数个空白字符,后接解释器的绝对路径,用于调用解释器。
/bin/bash是Bash的路径
sh调用:如果使用sh xxx.sh来调用脚本的话,则脚本中的shebang就没有作用了。
独立运行:如果用./xxx.sh 或者/direc/xxx.sh 让脚本独立运行则需要shebang,且需要对应用户的执行权限。
如果是独立运行,shebang生效,相当于$ /bin/bash xxx.sh
打开一个用户终端的时候,Linux会执行一组命令来定义一些基本配置,如颜色,字体等,我们可以在这里打印一些语句,那么语句就会在终端开启时展示出来。这组命令位于~/.bashrc,它相当于是Shell的配置文件。~/.bash_history保存了该用户的历史命令。
username@hostname:~$ 也是可以通过在~/.bashrc中修改,
slynux@localhost: ~$ PS1="PROMPT>"
PROMPT> Type commands here # Prompt string changed.
甚至可以修改为带格式带颜色的内容
在~/.bashrc中添加PS1="\e[1;31m[ROOT-47.107.97.119-172.18.44.32 ~] # \e[0m"每次打开终端都会生效。
分号或换行符都代表一个命令的结束
即:
com1 ; com2
与
com1
com2
效果相同。
第二种写法可以在行尾加#添加注释。
终端打印
echo
自带换行符。也可以-n来忽略结尾换行符
单引号各种特殊符号都会原样显示,$var也不例外,它不会输出引用值,而是直接输出‘$var’
双引号会将各种特殊符号的含义表示出来,如果需要原样展示需要在之前加入转义字符(\)
如果不加引号,那么无法显示分号,分号会被当做分割命令的标志。如echo hello ; world中world会被当做第二个命令序列执行
echo -e "\e[1;31m This is red text \e[0m" #设置颜色
printf
可以设置字符宽度,对齐方式,颜色等,需要手动设置换行符
printf "%-5s %-10s %-4.2f\n" 1 Sarath 80.21
%f %d %s %c 可以控制文本的输出样式
printf "\033[42;35mhello\033[0m"; #\033[字背景颜色;字体颜色m字符串\033[0m
颜色与背景颜色
Color codes are used to represent each color. For example, reset=0, black=30, red=31, green=32, yellow=33, blue=34, magenta=35, cyan=36, and white=37
For a colored background, reset = 0, black = 40, red = 41, green = 42, yellow = 43, blue = 44, magenta = 45, cyan = 46, and white=47, are the color code that are commonly used.
变量与环境变量
Shell环境与操作系统预设置的一些变量,存放入值,这些变量是环境变量
用户定义的变量无论是否带引号,都会被Bash存储为字符串
查看某个进程程序的环境变量:
ps -ef|grep 进程 #找到$PID
cat /proc/$PID/environ #与该进程相关的环境变量以key1=value1\0key2=value2...的是个展示
cat /proc/$PID/environ |tr '\0' '\n' #比较方便查看
环境变量是在当前进程中没有定义,从父进程中继承而来的:
HTTP_PROXY=http://192.168.0.2:3128 ; export HTTP_PROXY
这里定义后,当前shell脚本中执行的任何程序都会继承该环境变量。在test-export1.sh中定义上述变量,在test-export2.sh中echo此变量,可以查看效果(直接在1脚本中用 路径/test-export2.sh的方式进行调用。此种方式为fork,是在1脚本下开一个子shell,执行完后回归到父shell。另外source是在同一个shell中执行两个脚本,相当于先合并再执行;exec也是在同一个shell中执行,但执行完2脚本后,1脚本中的内容就不会再执行。)
注意var=3和var = 3不同,前者表示变量定义,后者表示一种相等的判断
var=123456789;echo ${#var} #字符串长度
识别当前shell脚本
[root@iZhp3e05oxm4uwwhsdq2vuZ script-learn]# echo $SHELL
/bin/bash
[root@iZhp3e05oxm4uwwhsdq2vuZ script-learn]# echo $0
-bash
$UID 表示当前用户的UID变量值,root的UID为0,所以可以通过[ $UID -ne 0 ]来判断是否为root用户(不等于0即true表示非root用户)
数学运算
变量定义出来都会被Bash存储为字符串,但也有办法让数字字符串按照数字处理。
no1=4;no2=5;let result=no1+no2;echo $result #使用let对数字字符串进行数字处理
no1=4;no2=5;result=$[$no1+no2 ];echo $result #[ ]处理数字字符串
no1=4;no2=5;result=$(($no1+no2));echo $result #(( ))处理数字字符串
no1=4;let no1++;let no1--;let no1+=6;echo $no1 #自加自减以及简写
以上三种处理方式中出现的变量名称,可以带$也可以不带。
另外,也可以使用expr处理,但要求变量名需要带$,并且变量或者数字字符之间要有空格
no1=4;no2=5;result=`expr 3 + 4`;echo $result;result2=$(expr $no1 + 12);echo $result2 #两种处理方式
以上处理方式都只支持整数,不支持浮点型。
bc被当做linux中的计算器,支持一些高级计算
echo '2+3' | bc;echo '5-2' | bc;echo '2+3*1' | bc #加减乘除
echo 'scale=2; (2.777 - 1.4744)/1' | bc #保留2位小数
echo "ibase=2;111" | bc; #二进制转为十进制
abc=192;echo "obase=2;$abc" | bc #十进制转二进制
abc=11000000 ;echo "obase=10;ibase=2;$abc" | bc #注意进制转换时,obase尽量写在ibase之前,十六进制时字母用大写 #ibase理解为输入进制,obase理解为输出进制,abc是输入,求输出,可以是2,8,10,16分别代表对应进制
echo "sqrt(100)"|bc;echo "10^10"|bc #平方根、指数计算
文件描述符与重定向
0 – stdin (standard input);1 – stdout (standard output);2 – stderr (standard error)
echo "This is a sample text 1" > temp.txt #清空后写入,>默认使用标准输出,等同于 1>
echo "This is sample text 2" >> temp.txt #追加后写入,>>默认使用标准输出,等同于 1>>
ls + >out.txt #stderr信息会被输入到屏幕上,且out.txt中没有内容,因为>等同于1>
ls + 2>out.txt #stderr信息会被输入到out.txt中
cmd 2>stderr.txt 1>stdout.txt #不同的信息输入到不同的文件中
cmd 2>&1 out.txt #2和1都被指定到同一个文件中
cmd &> out.txt #与上同
如果不想屏幕输出,也不想输出到某个文件,就直接 command 2> /dev/null。这里/dev/null是设备文件,这个文件接收到任何数据都会直接丢弃,也被称为黑洞或者位桶(bit bucket)
cat /script-learn/test-export.sh > /dev/null |grep exec # 标准输出到文件中后,也就没什么流入管道(|)被后续的命令使用:
cat /script-learn/test-export.sh | tee /dev/null | grep exec #
tee可以将后续的stdout复制一份,一份放入文件,另一份作为stdin流入后续命令
tee直接使用默认覆盖文件,用-a表示追加
A command that reads stdin for input can receive data in multiple ways. Also, it is possible to specify file descriptors of our own using cat and pipes. #理解:如果要通过读取stdin的内容作为输入的命令,其实有很多种方法。也可以通过cat和管道来制定我们自己的描述符(一个自己写的命令组合)来处理文件。
如 cat file |cmd1; cmd2 | cmd3
cmd < file #这里就可以不用cat,直接用重定向作为cmd的stdin。
另外,可以用cat <<EOF>log.txt 实现多行输入。
需要往一个文件里写多行内容时,都是 echo ......1 >xxx.txt; echo ......2 >xxx.txt;...;echo ......N >xxx.txt
现在可以用这种方式 cat <<eof >log.txt;...1;...2;......;...N;eof;来书写,方便易读。也可以 cat >log.txt <<eof;.....;eof
上述写法是覆盖,cat >>log.txt <<eof;.....;eof 表示追加
该种方式既可以在控制台实现,也可以在脚本中实现
如果是在控制台,也可以直接以cat >log.txt开始,写完后,以Ctrl+D表示eof结束。
eof表示end of file ,只是一个标记,可以换成别的字符串组合。
自定义文件描述符号:
exec 3<input.txt;cat <&3 #相当于cat input.txt,即3被当作<input.txt使用
exec 4>output.txt ; echo newline >&4; #相当于echo newline > output.txt
即定义一个自定义符号,再在后续的命令中使用,注意这里只能使用一次,再次使用需要再次定义
exec 5>>append.txt;echo newline >&5 #追加一行到文件中,注意后续的>&
数组与关联数组
定义普通数组 array_var=(1 2 3 4 5 6)
array_var[0]="test1";array_var[1]="test2";array_var[2]="test3";array_var[3]="test4";array_var[4]="test5";array_var[5]="test6"
echo ${array_var[0]} #取第0个元素
echo ${array_var[*]};echo ${array_var[@]} #都表示全部取出
也可以用变量
index=3;echo ${array_var[$index]} # 如果数组越界了,会输出空
定义关联数组 需要先声明,再定义。如:
declare -A fruit_prices; #声明一个关联数组
fruit_prices=([apple]="100 dollars" [orange]="20 dollars"); #定义每个文本索引的内容,如果有空格这里不能直接写值
echo ${fruit_prices[orange] # 对应元素取值
echo ${array_var[*]};echo ${array_var[@]} #都表示全部取出
declare -p #查看所有的环境变量,如果是在控制台声明,可以在这里看到
unset 变量名 #从内存中释放掉该变量
别名
alias new_cmd="cmd sequence ..."
命令的作用只是暂时的,一旦关闭当前终端,所有设置过的别名就失效了。如果需要,可以将其放入~/.bashrc中。
如果需要删除别名,使用unalias xxx
alias lx="ls";lx;unalias lx;lx
比如对rm进行处理:
alias rm='cp $@ ~/backup;rm $@' #使用rm删除时,先将其保存在backup中
在不信任的环境下,可以使用转义符确定使用的是没有被加入别名的命令,以避免一些安全问题。
alias ls="pwd";
ls
\ls
......
unalias ls
获取终端信息
主要是tput和stty两种终端处理工具。
目前都是XShell来进行配置相关样式,这两类命令较少使用。
日期和延时
类Unix系统中,日期被存储为一个整数,是以1970-01-01 00:00:00开始之后的秒数,这种计时被称为UNIX时间。
date 可以查看系统默认格式的时间
date --date "20201128 11:23:00" +%a #自己根据某个格式的时间展示出自己想要的信息,这里会支持多种格式,只要不是太生僻的格式,一般都支持,可以先试试,实在不支持就用"yyyy-MM-dd HH:mm:ss"的格式,不会错。
%A %a %B %b 星期/月份的英文全拼/缩写
%D 不用%m/%d/%y输出,简化了
%d 日 数字
%Y %y 年份全称/后两位 数字
%m 月份 数字
%M 分钟 ..
%H 小时 ..
%S 秒 ..
%N 纳秒 ..
%s Unix纪元秒数
date +%Y-%m-%d
date "+现在时间:%Y-%m-%d %H:%M:%S"
也可以设置系统时间
date -s "20201128 11:07:31" #格式上要相信date很智能
sleep可以在脚本中推迟执行,单位为秒。
Debug
bash -x script.sh #会将执行的内容打印在控制台
在脚本中
set -x #执行时显示参数和命令
set +x #禁止调试
set -v #命令进行读取时显示输入
set +v #禁止打印输入
#! /bin/bash -xv #脚本中直接启用调试功能
注意:如果是sh xxx.sh那么上面定义的一行不会生效哦,这个时候是不会开启调试的
分析这个函数:
function DEBUG(){
[ "$_DEBUG" == "on" ] && $@ || :
}
#1. [ "$_DEBUG" == "on" ]表示条件判断,环境变量_DEBUG的值是否为on
#2. 这条语句构成了 cond && cmd1 || cmd2,表示cond为真则执行cmd1,否则执行cmd2
#3. $@表示把调用该函数的参数当作命令来执行
#4. :表示什么都不做
#5. 整条语句的意思即,如果_DEBUG值为on,那么把传入的参数当作命令执行,否则什么都不做。
_DEBUG=on ./test-debug.sh # 注意该语句的含义
函数
function fname(){
statements;
}
fname(){
statements;
}
fname; #执行函数
fname arg1 arg2; #传递参数
同样地,参数也可以传给script脚本,在脚本中使用也可以用$N来表示第N个参数
$N 第N个参数
$@ 相当于“$1”“$2”“$3”......
$* 相当于 “$1 $2 $3 ......”
$0 表示脚本名称
bash中支持递归调用--fork炸弹
函数也能用export导出,就像环境变量一样。这样函数的作用域就可以扩展到子进程中。
export -f fname;
命令
读取命令执行状态
$? # 成功退出状态为0,否则为非0。
对于函数中的return数值,Bash中的取值范围是0-255,如果超过这个范围,echo $?数值会给出与预期不符的结果。
所以想要函数的返回值,可以将需要返回的值写入某个变量,后续逻辑中直接使用该变量;
也可以在函数中echo想要输出的值,在后续调用中使用result=`test xxx`来接受需要输出的值;
向命令传递参数:
对于可选参数,可分可合可与需要指定内容的参数合并,如:
$ cmd -p -v -k 1;cmd -pv -k 1;cmd -pvk 1
如果用文件作为参数,可以
$ cmd file # 具体地内容可以与其它参数灵活放置,如:
$ cmd -p -v -k 1 file ;cmd -pv -k 1 file ;cmd file -pvk 1;cmd -pvk 1 file
读取命令系列输出
对于一个命令,通常是通过stdin或者参数把要处理的原料传递进来。
ls | cat -n > out.txt # ls的输出被传给cat,cat -n为通过stdin所接收到的输入内容加上行号,然后将输出重定向到文件out.txt中。
将命令结果传递给变量存储:
cmd_out=`ls |cat -n`;echo $cmd_out #反引用
cmd_out=$(ls |cat -n);echo $cmd_out #子shell
可以使用()来定义一个子shell。
pwd;(cd /etc;ls);pwd # 命令在子shell中执行时,不会对当前shell有任何影响,所有的改变仅限于子shell内。
另外,注意使用双引号和不适用双引号的 引用子shell区别
echo $(cat -n log.txt) # 会被合并为一行
echo "$(cat -n log.txt)" # 会保留行尾的\n
out=$(cat -n log.txt);echo $out;echo "$out" # 这里的区别也与上述内容相同
注意,如果文本结尾是几个换行符的话,"$(cat log.txt)"也不会显示,但"$(cat -n log.txt)"会标好行号并打印,注意区别。
read
read可以从键盘或者标准输入读取文本。该命令提供了各种场景下的读取,方便使用。
$ read -n 2 var # 从输入中读取2个字符
>
echo $var
read -s var # 不显示,secret
read -p 'Please enter password:' var # 提示
比较常见的是:
read -sp 'Please enter password:' var #提示'Please enter password:',输入时不显示输入内容
read -t 2 var # 限定时间2秒内的输入
read -d ":" # 当读取到:时即结束读取
字段分隔符
IFS是存储定界符的环境变量,是当前shell环境使用的默认定界字符串
shell默认的IFS为换行符,制表符或者空格
基本思路是,先将默认的IFS存储到一个变量中,而后将IFS设置为分隔符号,再用foreach进行处理,处理完后将IFS复原
注意下方的例子中,不用if进行的一个简洁的表达:
#! /bin/bash
# Description: Illustration of IFS
line="root:x:0:0:root:/root:/bin/bash"
oldIFS=$IFS;
IFS=":"
count=0
for item in $line;
do
[ $count -eq 0 ] && user=$item;
# condition $$ cmd表示 condition为真时执行cmd;condition || cmd表示condition为假时执行cmd
[ $count -eq 6 ] && shell=$item;
let count++
done;
IFS=$oldIFS
echo $user\'s shell is $shell;
循环
for var in list # for循环
do
cmd
done
list表示一个序列,可以是分割出来的,可以是{1..50},{a..z},{A..Z}表示一个序列
也可以用fori的形式for((i=0;i<10;i++)){ action }# 此处也可以用do/done来表示大括号
while condition
do
cmd
done
until condition # 循环到condition为真时停止
do
cmd
done
IF/ELSE
if condition
then
cmd
fi
if condition
then
cmd1
elif condition
then
cmd2
else
cmd3
fi
比较
----算数比较----
[ $var -eq 0 ]
[ $var -ne 0 ]
-eq
-ne
-gt
-lt
-ge
-le
[ $var1 -eq 0 -a $var2 -ne 0 ] #逻辑与
[ $var1 -eq 0 -o $var2 -ne 0 ] #逻辑或
----文件比较----
[ -f $filePath/fileName ]
# 判断变量指代的文件是否存在,如果是当前路径,文件名可以不加路径。与-e相同。而-s要查看文件大小,文件大小不为0时才真。
[ -x $file ] /-r -w #后者表示可读,可写
# 文件是否可以执行。应该是检查当前执行脚本的用户是否有该文件的执行权限。TODO 在换个用户登录之后查看下是否还是可以执行
[ -d $directory ]
# 查看文件路径是否存在
-c / -b # 是一个字符设备文件/块设备文件的路径
-L 判断是否为链接,注意判断链接时末尾不要加"/"
----字符串比较----
最好用双中括号(单中括号可能会产生错误)
[[ $str1 = $str2 ]] 或者 [[ $str1 == $str2 ]] 都可以表示两个字符串是否相同
[[ $str1 != $str2 ]] 判断是否不相同
[[ $str1 > $str2 ]] 或者 [[ $str1 < $str2 ]] 表示字母排序
[[ -z $str1 ]] 是空字符串则真
[[ -n $str1 ]] 不是空字符串则真
关于逻辑符号的理解
逻辑运算符&&和||可以把多个条件组合起来。之前遇到的[] && cmd, [] || cmd, [] && cmd1 || cmd2其实原理都是,第一个条件为真/假时,shell会执行后续的命令。根据这个原理可以很容易地把多个判断与命令结合起来。
另外,if后的条件写法,只要是一个判断表达式即可,并不是非要if [ ]的格式,可以是if [[ ]] && [ ]的格式。