是什么
壳程序,与应用程序和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 ]
空格隔开
- 整数比较
-eq
(==),-ne
(!=),-lt
(<),-le
(≤),-gt
(>),-ge
(≥)
[ 23 -lt 22 ]
echo $? # 非true 返回非0
-
字符串判断
==
!=
-z
空字符串-n
非空字符串 -
文件权限判断
-r
-w
-x
[ -w test.sh ] # test.sh是否有写权限
echo $? # 最后一次的命令是否正确执行
- 文件类型判断
-e
存在?-f
存在且为文件?-d
存在且为目录?
[ -e /home/xxx/cls.txt ] # cls.txt是否存在
echo $? # 最后一次的命令是否正确执行
- 多条件判断
&&
前一条命令执行成功(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
函数
系统函数
- basename
basename [string/pathname][suffix]
提取路径里的文件名称 加上后缀选项例如.txt 则会将后缀一并去掉
basename /home/xxx/test.sh # test.sh
basename /home/xxx/test.sh .sh # test
- dirname
获取文件绝对路径
dirname /home/xxx/test.sh # /home/xxx
- 自定义函数
#!/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
:deletei
:插入 出现在目前行的上一行p
:prints
:替换 ‘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"