SHELL脚本学习笔记

是什么

壳程序,与应用程序和Linux内核打交道
是用户和操作系统之间的命令行解释器

查看Linux支持的Shell解析器

cat /etc/shells

shell脚本

批处理 程序语言 无需编译
用于自动化管理 服务器的侦测 一些日常工作

touch test.sh
vim test.sh

写入内容

#!bin/bash # 指定shell解析器,用来告知系统如何执行该脚本
# 每一句指令以换行或分号隔开:
echo "testtesttest" # 写入内容

# 声明一个变量:(不隔开)
Variable="Some string"

执行shell脚本 相对路径和绝对路径都可

执行方式

  • 通过bash执行的话不需要脚本有执行权限
  • 输入脚本绝对路径/相对路径自己执行(需要执行权限x
  • 脚本路径前 + “.”
  • source命令执行sh

前3种执行方式都是使用子程序bash执行的 变量使用完就会被清除 父程序看不到 只有使用source命令时才在父程序中执行 ;其次 子进程只继承父进程的环境变量 自定义变量子进程也看不到 除非export成环境变量

bash ./test.sh # 通过bash来执行
./test.sh # 脚本本身自己执行自己
source test.sh

权限不够的话需要添加执行权限x

r-4 w-2 x-1
chmod +x test.sh
chmod 744 test.sh

shell脚本跟踪和debug

sh -x scripts.sh  # 将使用到的脚本内容显示到屏幕中
sh -v scripts.sh  # 执行脚本前 先将内容输出到屏幕 
sh -n scripts.sh  # 不执行脚本 仅检查语法问题

vim 编辑模式 : set nu 显示行号 set nonu取消行号

变量

变量声明

# 等号赋值
name="echox"

# 全局变量
export variable=value

# 该命令可以声明变量的类型,如整数型、数组型等
declare -i age=25
declare -a fruits=(“apple” “banana” “orange”)

命名规则

  • 变量名只能是英文字母与数字 不能以数字开头
  • 变量内容有空格的话用引号扩""
  • 双引号内的特殊字符可以保有原本特性 单引号则为纯文本 转义\可将特殊符号变成一般字符
"lang is $LANG" ==> lang is zh_CN.UTF-8
'lang is $LANG' ==> lang is $LANG  

系统变量(环境变量)

  • 环境变量大写
  • 全局变量
set # 列出当前shell中所有变量

可以用echo将变量的值输出到std output, 变量前加$

echo &{HOME}

自定义变量

  • 定义:变量名=变量值,无空格,如果变量值里有空格需要加上双引号
  • 撤销变量 unset 变量名
  • 静态变量 定义前面加readonly 不能unset
  • 升级全局变量: export 变量名;全局可用 否则子shell不可用
B=100
A=100
unset A
echo ${B}
export B

扩充变量内容

name="Echox"
name=${name}yes  #在原变量内容中增加"yes"
name="$name"yes  #效果同上  
name="${name}yes" #效果同上  

一个指令可用 $( ) 嵌套在另一个指令内部

var = $(conmmand) # 先执行括号内命令 再输出
cd /lib/modules/$(uname -r)/kernel
echo $(date)

反斜杠用于转义 \\\\\\\\\\\\\\\\\\\\\

name=VBird\'s\ name # 转义特殊符号'

特殊变量

  • $n n为数字,$0代表脚本名称,$1-$9代表第一到第九个参数 ${10}10以上的参数用{}包住
  • $# 输出参数个数(不包含脚本名称)
  • $* 输出所有参数(不包含脚本名称) 所有参数看为一个整体
  • $@ 输出所有参数(不包含脚本名称) 每个参数区分对待
  • $? 返回0(true) 说明最后一次执行的命令正确执行 非0则执行有问题

变量关系

启动shell时 os会分配一块内存区域给shell 当加载另一个shell时(相当于子进程)
子shell会把父shell的环境变量所在内存区域导入到自己的环境变量区块中
当父shell export某个自定义变量时 就是将变量写入到那块内存区域 所以后续可以被子shell导走

语系变量

locale 设置语系编码 如果出现乱码 则更改这个显示语系

cat /etc/locale.conf # 修改配置文件

# 直接修改LANG变量 然后导出
LANG=en_US.UTF-8; locale
export LC_ALL=en_US.UTF-8; locale

运算符

$((运算式)) or $[运算式]

S=$[1+5*7]
S=$((1+5))

条件判断

[ condition ] 空格隔开

  1. 整数比较
    -eq(==), -ne(!=), -lt(<), -le(≤), -gt(>), -ge(≥)
[ 23 -lt 22 ]
echo $? # 非true 返回非0
  1. 字符串判断
    == != -z 空字符串 -n非空字符串

  2. 文件权限判断
    -r -w -x

[ -w test.sh ] # test.sh是否有写权限
echo $? # 最后一次的命令是否正确执行
  1. 文件类型判断
    -e存在? -f存在且为文件? -d存在且为目录?
[ -e /home/xxx/cls.txt ] # cls.txt是否存在
echo $? # 最后一次的命令是否正确执行
  1. 多条件判断

&&前一条命令执行成功(true) 才执行后一条命令 ||前一条命令执行失败(false) 才执行下条命令

[ -e ./cls.txt ] && echo OK || echo notOK 
# 不存在则输出notOK
echo $? && echo OK || echo notOK  # $?总是能成功执行 则一直输出OK
test -r filename -a -x filename # 判断是否同时成立 and

流程控制

if判断

单分支

if [ condition ];then
    // todo
fi 

if [ condition ]
then 
    // todo
fi 

if [ -d directory ]; then
    # 如果 directory 存在且是一个目录,则执行的代码
else
    # 如果 directory 不存在或者不是一个目录,则执行的代码
fi

多分支

if [ condition ]
then 
  // todo
elif [ condition ]
then 
  // todo
else 
  // todo
fi 

判断输入参数是否=1 =1 输出case1 =2 输出case2 其他 输出case3

#!/bin/bash
if [ $1 -eq 1 ]
then 
  echo "case1"
elif [ $1 -eq 2 ]
then 
  echo "case2"
else 
  echo "case3"  
fi 

case语句

#!/bin/bash
case &变量名 in
"值1")
  // todo
;;
"值2")
  // todo
;;
*)
  // todo
;; 
  // todo
esac 

for循环

#!/bin/bash
sum=0
for ((i=0;i<=100;i++))
do
  sum=$[$sum+$i]
done
echo $sum
#!/bin/bash
for i in cls mly wls
do
  echo "xxx is $i"
done
#!/bin/bash
for i in {1..254}
do
  echo "xxx is $i"
done

$* 输出所有参数 加双引号的时候 所有参数看为一个整体 "$1 $2 $3 4 " ‘ 4" ` 4"‘@` 输出所有参数 加双引号的时候 每个参数区分对待 “$1” “$2” “$3” “$4”

#!/bin/bash
for i $*  # 这里替换成$@ 输出无区别
do
  echo "xxx is $i"
done
echo "与下面效果一致"
for i "$@" # 单个输出  换成$* 输出一个整体
do
  echo "xxx is $i"
done

while循环

#!/bin/bash
while [ condition ]  
do 
    //todo
done

read 读取控制台输入的变量

read [-tp] -t(读取等待时间:s) -p(提示) variable

read -t 10 -p "please input your name in 10s:" name
echo $name

函数

系统函数

  1. basename
    basename [string/pathname][suffix]
    提取路径里的文件名称 加上后缀选项例如.txt 则会将后缀一并去掉
basename /home/xxx/test.sh # test.sh
basename /home/xxx/test.sh .sh # test
  1. dirname
    获取文件绝对路径
dirname /home/xxx/test.sh # /home/xxx  
  1. 自定义函数
#!/bin/bash
function sum()
{
	s=0
	s=$[$1+$2]
	echo ${s}
}
read -p "please input the num1: " n1;
read -p "please input the num2: " n2;
sum ${n1} ${n2};

管线命令(pipeline)

仅处理标准输出 接受来自前一个指令的数据成为input继续处理

cut

cut 用于排列整齐的讯息 取出分隔后的某一段 自由切割

#选项与参数:
#-d :后面接分隔字符。与 -f 一起使用;
#-f :依据 -d 的分隔字符将一段讯息分区成为数段,用 -f 取出第几列的意思,取出某段后面的所有 数字后面再加-
#-c :以字符 (characters) 的单位取出固定字符区间;
cut -f 2 filename.txt			# 提取某列
cut -f 1,3,5 filename.txt		# 提取多个列
cut -d "," -f 2 filename.csv	# 用特定分隔符提取某一列
echo "Hello, World!" | cut -c 1,8-13	# 提取指定字符位置

grep

非常常用的命令

cat Log/file.txt | grep -i keywords  # 会返回这个文件中包含这个keywords的行 

可以用grep --h查一下使用说明

grep --h

Regexp selection and interpretation:
 -i, --ignore-case         ignore case distinctions
 ...
Miscellaneous:
 -v, --invert-match        select non-matching lines
 ...
Output control:
 -o, --only-matching       show only the part of a line matching PATTERN
 ...
Context control:
 -B, --before-context=NUM  print NUM lines of leading context
 -A, --after-context=NUM   print NUM lines of trailing context
 -C, --context=NUM         print NUM lines of output context
 ...
正则表达式
cat /etc/passwd | grep Echox # 匹配所有包含Echox的行
cat /etc/passwd | grep ^a    # 匹配所有以a开头的行
cat /etc/passwd | grep t$    # 匹配所有以为t结尾的行
cat /etc/passwd | grep r..t  # 匹配任意一个字符 rxxt
cat /etc/passwd | grep r.*t  # 匹配任意0个或多个字符 rxxt rxxxt
cat /etc/passwd | grep go*d  # 匹配0个或多个前面重复的字符 gd god good goood ....    

匹配字符区间内的一个字符[]

  • [6,8] 匹配6 or 8
  • [0-9] 匹配一个0-9的数字
  • [0-9]* 匹配任意长度数字字符串
  • [a-c,e-f] 匹配a-c或者e-f之间的任意字符
  • [:alnum:], [:alpha:], [:upper:], [:lower:], [:digit:] [[]]

转义字符 \

cat /etc/passwd | grep 'a\$b' # 匹配所有包含a$b的行 要用单引号包起来   

这里的[]区别于前面的条件判断 [ condition ] 用空格来区分

grep -n "the" regular_express.txt   # 查找所有包含'the'的行 显示行号
grep -vn "the" regular_express.txt  # 查找所有不包含'the'的行 显示行号
grep -in "the" regular_express.txt  # 不区分大小写
grep -n "t[ae]st" regular_express.txt  # 查找所有不包含'the'的行 显示行号
grep -n "^[a-e]" regular_express.txt  # ^在[]外部, 查找所有以a-e开头的行
grep -n "[^a-e]" regular_express.txt  # ^在[]内部, 反向选择 ^[^a-d] 筛选不包含a-e开头的
grep -n 'go\{2,3\}g' regular_express.txt  # 连续 n 到 m 个的"前一个 RE 字符" 意义
grep -n 'gog|do' regular_express.txt  # 查找包含gog或者do的行
grep -n 'g(og|do)d' regular_express.txt # 查找包含gogd或者gdod的行
grep -n 'go+d' regular_express.txt  # 查找包含god good goood 重复一个or一个以上的re字符
grep -n 'go?d' regular_express.txt  # 查找重复一个or 0个的re字符

sort

排序

sort [-fbMnrtuk] [file or stdin]
#选项与参数:
#-f :忽略大小写的差异,例如 A 与 a 视为编码相同;
#-b :忽略最前面的空白字符部分;
#-M :以月份的名字来排序,例如 JAN, DEC 等等的排序方法;
#-n :使用“纯数字”进行排序(默认是以文字体态来排序的) ;
#-r :反向排序;
#-u :就是 uniq ,相同的数据中,仅出现一行代表;
#-t :分隔符号,默认是用 [tab] 键来分隔;
#-k :以那个区间 (field) 来进行排序的意思

uniq

查找和删除文件中连续重复行 或者计数

uniq filename.txt
uniq -c filename.txt	# 统计文件中重复行的数量
uniq -d filename.txt	# 仅显示连续的重复行
uniq -u filename.txt	# 仅显示不重复行

wc

列出有行数 单词数 字符数

wc file		# 统计filename.txt文件中的行数、单词数和字符数
cat /etc/passwd | wc -l -# 统计行数
wc -w -# 统计单词数
wc -c -# 统计行数

split

将文件根据文件大小或行数来分区
然后可以使用 >> 数据重定向进行文件合并

split -l 10 file.txt split # 将file.txt按照行来划分, 划分为前缀名是split的子文件 splita splitb...
cat split* >> merge # 将这些小文件合并为一个文件merge

其他工具

sed工具

分析关键词的使用、统计等 数据替换、删除、新增、选定特定行

  • a:在下一行新增内容
  • c:替换字符 某几行替换成一行 内容就是替换的字符
  • d:delete
  • i:插入 出现在目前行的上一行
  • p:print
  • s:替换 ‘s/要被替换的字符/新字符/g’ 有g的话表示这一行所有匹配到的字符串都进行替换 没有的话只替换匹配到的第一个字符串
sed [-选项] [[n1,n2]操作行为] [要更改的文件]
nl regular_express.txt | sed '1,2d' # 删除1-2行
nl regular_express.txt | sed '2d' # 删除第2行
nl regular_express.txt | sed '2,$d' # 删除第2到最后一行, $代表最后一行
nl regular_express.txt | sed '2a add sth here' # 在第2行之后插入'add sth here'
nl regular_express.txt | sed -n '5,7p' # 打印5-7行 只显示变化的这几行
nl regular_express.txt | sed -n 's/要替换的字符/新的字符//g' 
ifconfig eth0 | grep 'inet ' | sed 's/^.*inet addr://g' | sed 's/ *Bcast.*$//g' # 字符替换成空 类似于删除 实现提取本机ip

awk工具

每次处理一行 将一行分成数个字段来处理 适合小型文本数据

awk '条件类型1{操作1} 条件类型2{操作2} ... ' filename
last -n 5 | awk '{print $1 "\t" $3}'  # 字段以`tab`分隔 取出第一栏和第三栏 这里用变量写法 $0表示一整列数据

内置变量
NF:每行拥有的字段总数 (列个数)
NR:目前awk处理的是第几行数据 (输出行号)
FS:目前的分隔字符, 默认是空格分隔

cat /etc/passwd | awk '{FS=":"} $3 < 10 {print $1 "\t" $3}' # 操作1:以":"作为分隔 条件类型:找出第三栏<10的 操作2:打印1,3栏 以tab作为分隔

第一行读入时还是以默认空格进行分隔, 新的在第2行才开始生效 可以用BEGIN关键字预先设置awk的变量

cat /etc/passwd | awk 'BEGIN {FS=":"} $3 < 10 {print $1 "\t" $3}' # 操作1:以":"作为分隔 条件类型:找出第三栏<10的 操作2:打印1,3栏 以tab作为分隔
cat pay.txt | \
> awk 'BEGIN {if (NR==1) printf "%10s %10s %10s %10s %10s\n", $1,$2,$3,$4,"total" } # 第一行不作处理 只是规范格式 增加total列
> NR>=2{total = $2 + $3 + $4
> printf "%10s %10d %10d %10d %10.2f\n",$1,$2,$3,$4,total}'

cat pay.txt | \
> awk 'NR==1{printf "%10s %10s %10s %10s %10s\n", $1,$2,$3,$4,"total" } --> 另一种写法

模式匹配

awk [选项参数] '/pattern1/{action1} /pattern2/{action2}...' filename
awk '/^root/{print $1 "," $7}' passwd # /^root/正则匹配 用//包裹 搜索passwd文件中以root关键字开头的所有行 打印第1列和第7列 

example - log告警脚本

#!/bin/bash

# program:
# check part usage, if part usage > threshold, alarm!

PARTED_PATH="/home"
ALARM_LOG="/root/test/alarm.log"
# 5s check一次
CHECK_INTERVAL=5
THRESHOLD=1

i=1
while [ $i -le 5 ]; do
        part_usage=$(df -h "$PARTED_PATH" | awk 'NR==2{print $5}' | sed 's/%//g')
        if [[ "${part_usage}" -gt ${THESHOLD} ]];then
                echo "$(date '+%D %H:%M:%S') Warning: The parted_section ${PARTED_PATH} usage is ${part_usage}% , more than threshold ${THRESHOLD}% " >> ${ALARM_LOG}
        fi
	let i++
        sleep "$CHECK_INTERVAL"
done

数据重定向

本来要输出到屏幕的数据重新导向到其他的地方

  • 标准输入(stdin)
    < , <<(累加,一般添加用于结束的输入字符EOF)
  • 标准输出(stdout):命令执行返回的正确信息
    >(覆盖) , >>(累加)
  • 标准错误输出(stderr):命令执行返回的错误信息
    2>(覆盖), 2>>(累加)
cat > hello.py << EOF
#!/usr/bin/env python
from __future__ import print_function
import sys
print("#stdout", file=sys.stdout)
print("#stderr", file=sys.stderr)
for line in sys.stdin:
    print(line, file=sys.stdout)
EOF

# 重定向可以到输出,输入和错误输出。
python hello.py < "input.in"
python hello.py > "output.out"
python hello.py 2> "error.err"
python hello.py > "output-and-error.log" 2>&1 # 将标准错误重定向到标准输出
python hello.py > /dev/null 2>&1
# > 会覆盖已存在的文件, >> 会以累加的方式输出文件中。
python hello.py >> "output.out" 2>> "error.err"

# 覆盖 output.out , 追加 error.err 并统计行数
info bash 'Basic Shell Features' 'Redirections' > output.out 2>> error.err
wc -l output.out error.err

补充

nohup

nohup是一个可以在后台运行命令,并忽略终端断开或退出的命令

nohup command &
nohup ./app server > /dev/null 2>&1 &

nohup会将命令的所有输出重定向到一个名为nohup.out的文件中,这样即使终端关闭,命令也会继续运行。

文件格式
用window打开过的shell文件放到linux下执行会有点问题;需要修改sh格式

vim 模式下
:set ff=unix -- linux
:set ff=doc -- window

如果出现 /bin/bash^M,可以批量替换空格

sed -i 's/\r$//' *.sh

实时查看日志

tail -f logfile.log

批量注释

[root@web01 ~]# cat oldboy.sh
#!/bin/bash
:<<EOF
echo "I am oldboy"
echo "I am oldboy"
echo "I am oldboy"
EOF #<==顶格写
echo "I am young"
[root@web01 ~]# sh oldboy.sh
I am young

批量修改 type

for x in $(find . -type f);do dos2unix $x $x;done

解压文件tar

tar -zxvf xxx.tar.gz -C xxxx

tar -cf archive.tar foo bar
# Create archive.tar from files foo and bar.

tar -tvf archive.tar
# List all files in archive.tar verbosely.

tar -xf archive.tar
# Extract all files from archive.tar.

三种引号

双引号
1.定义字符串,解析变量值
2.解析转义字符

NAME="John"
echo "Hello, $NAME"
# Hello, John

单引号

1.定义字符串,里面有变量的话不解析
2.单引号中的特殊字符都视为普通字符, 不需要转义;但如果单引号中还需要插入单引号,需要插入\做分隔

echo 'I don'\''t like apples'

BodyJson='{"logInfo":'"$logInfo"'}' == BodyJson="{\"logInfo\":$logInfo}"

反引号

古早的用法 用于执行包含命令 现在推荐用$()来替代
两者等效

DATE=`date` echo "Today is $DATE"
DATE=$(date) echo "Today is $DATE"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值