引言
Shell 是 linux 系统下非常实用的工具。通过使用 Shell,可以提升在 linux 系统下的工作效率。
Shell 学习
代码都在这里:https://github.com/xiang2017/shell_study
变量
#!/bin/bash
# 变量
echo "01_变量.sh"
# 变量定义与赋值,等号两边不能用空格分开
name=hahahaha
echo $name
echo 也可以使用 {} 输出: ${name}
# 一些特殊变量
test_func() {
echo "function name is $FUNCNAME"
}
test_func
echo $HOSTNAME
echo $HOSTTYPE
echo $MATCHTYPE
echo $LANG
echo $PWD
# echo $PATH
unset name
echo $name
# 只读变量
readonly R0=100
R0=200
echo $? # 上一条指令是错误的,所以 $? 为非0
# 变量的作用域
# 变量的作用域又叫“命名空间”,相同名的变量可以在不同命名空间定义。
# 在 Linux 系统中,不同进程 ID 的 Shell 默认为不同的命名空间
VAR_01=100
function update() {
# 在函数内外访问到的是同一个变量
VAR_01=200
}
update
echo 变量 VAR_01: $VAR_01
function update02() {
# 可以使用 local 关键字声明函数内部的局部变量
local VAR_01=300
}
update02
echo "local 声明的本地变量不会影响全局变量,VAR_01: ${VAR_01}"
# 子 Shell 不会继承变量
echo "echo 子 shell 的 VAR_01 为 \$VAR_01" > tmp.sh
bash ./tmp.sh
# 导出变量(环境变量),子 Shell 可继承,相当于子 Shell 启动时复制了导出的变量
export VAR_01
bash ./tmp.sh
# 在子 Shell 中修改 VAR_01 不会影响
rm ./tmp.sh # 删除 tmp.sh
转义和引用
#!/bin/bash
# 转义
# 跟其他编程语言里的转义一样,使用转义符 \
echo \# 使用转义输出注释符号 \#
Dollar=123
echo \$Dollar is $Dollar
echo 8 \* 8 = 64
# 引用
# Shell 中一共有 4 中引用符,分别是 双引号,单引号,反引号,转义符
# "" 双引号:部分引用,可以解释变量
echo "\$Dollar is $Dollar"
# 带不带双引号看起来一样,但是对于输出空格有区别
VAR="A B C"
echo 不带引号对于连续空格只输出一个:$VAR
echo "带引号会把所有空格输出:$VAR"
# '' 单引号:全引用,只按照字面意思输出内容,转义符也不能用了
echo '$Dollar 在单引号内还是 $Dollar。'
echo '转义符在单引号内输出 \,单引号只把内容作为字面量输出'
echo '转义符不能用,单引号内不能输出单引号'
# `` 反引号:命令替换,将命令的标准输出作为值赋给某个变量
# 命令替换也可以使用 $(命令) 的形式
LS=`ls`
echo "=== LS ==="
echo $LS
echo "=== LS ==="
LSA=$(ls -a)
echo $LSA
# $() 支持嵌套
$(echo $(ls) > tmp.sh)
TMP=$(cat tmp.sh)
echo === tmp ===
echo $TMP
echo === tmp ===
rm tmp.sh
运算符
#!/bin/bash
# 运算符
# Shell 的运算符主要有:
# 比较运算符(整数比较),字符串运算符(字符串测试),文件操作运算符(用于文件测试),逻辑运算符,算术运算符,位运算符,自增自减等
# 算术运算符:加减乘除余幂 以及加等,减等,乘等,初等,余等
A=1
B=2
let "C = $A + $B" # 需要使用 let 关键字执行运算
echo $C
# 位运算符:左移 右移 按位与 按位或 按位非 按位异或
var1=1
var2=5
let "var = $var1<<2"
echo $var
let "var = $var1&$var2"
echo $var
# 按位非就是 -($var+1)
let "var = ~8"
echo $var
# 自增自减,与其他语言类似,分为前置和后置的区别
var1=1
echo "var1 is $var1"
let "var2=++var1"
echo "var2 前置自增 var1,$var2"
var1=1
let "var2=var1++"
echo "var2 后置自增 var1,$var2"
# 其他算术运算
# 使用 $[] 做运算:$[] 和 $(()) 类似,可用于简单的算术运算
echo '$[1+1]' is $[1+1]
echo '$[5 ** 2]' is $[5 ** 2]
# 使用 expr 做运算:使用 expr 要求操作数和操作符之间用空格分开,否则会被当成字符串
expr 1+1
expr 1 + 1
expr 2 \* 2 # 特殊字符运算符需要转义
# 算术扩展: $((算术表达式))
echo $((2*(1+1)))
# 使用 bc 做运算
# 前面介绍的运算只能基于整数,如果想要计算高精度小数,可以使用 Linux 下的 bc 工具。
# bc 是一款高精度计算语言,支持顺序执行,判断,循环,函数等,下面是一个简单的例子
NUM1=1.2
NUM2=2.3
SUM=$(echo "$NUM1+$NUM2" | bc)
echo $SUM
# 你也可以直接在命令行下输入 bc,然后回车进入 bc 命令行模式
特殊字符
#!/bin/bash
# 特殊字符
# 通配符
# 通配符用于模式匹配,常见的通配符有 *、? 和用 [] 括起来的字符序列。
# 例如:a* 可以匹配以 a 开头的任意长度的字符串,但是不能包含 点号和斜线号
# 所以 a* 不能匹配 abc.txt
# ? 可以匹配任意单个字符
# [] 表示可以匹配其中的任意一个,比如 [abc] 可以匹配a或者b或者c
# [] 中可以用 - 表示起止。比如 [a-z] 匹配所有小写字母
# *? 在 [] 表示普通字符,没有通配功效
# 引号
# 02_转义和引用.sh 中介绍过,主要有单引号,双引号,反引号
# 注释符号
# 大括号
# 大括号 {} 在 Shell 中的用法很多
# 1. 变量扩展 ${PWD}
# 2. 通配符扩展
# 3. 语句块
# 通配符扩展的例子:
touch file_{A,B}
ls . | grep file
rm file_A
rm file_B
# 其他
# 位置参数
# $0: 脚本名本身
# $1,$2... 脚本的第一个参数,第二个参数...
# $# 变量总数
# $* $@ 显示所有参数
# $? 前一个命令的退出的返回值
echo $? # 正常退出,结果为 0
rm qweqweqweqwe
echo $? # 出现错误时,结果为 非0
# $! 最后一个后台进程的 ID 号
测试
#!/bin/bash
# 测试:程序运行过程中经常需要根据实际情况执行特定的命令,
# 比如,判断某个文件是否存在,如果不存在,可能需要先创建该文件
# ls tmp.sh
# echo $?
# 测试结构
# 1. test expression 使用 test 指令
# 2. [expression] 使用 []
# 文件测试
# 1. test file_operator FILE
# 2. [file_operator FILE]
test -e tmp.sh
echo $? # 不存在,上一个指令结果为 1
[ -e tmp.sh ]
echo $?
# 文件测试符,文件不存在时,均返回假
# -b FILE 当文件存在且是块文件时,返回真,否则为假
# -c FILE 当文件存在且是设备文件时,返回真,否则为假
# -d FILE 测试文件是否为目录
# -e FILE 测试文件或目录是否存在
# -f FILE 测试文件是否为普通文件
# -x FILE 判断文件是否为可执行文件
# -w FILE 判断文件可写
# -r FILE 判断文件可读
# -l FILE 判断是否为链接文件
# -p FILE 判断是否为管道文件
# -s FILE 判断文件存在且大小不为 0
# -S FILE 判断是否为 socket 文件
# -g FILE 判断文件是否设置了 SGID
# -u FILE 判断文件是否设置了 SUID
# -k FILE 判断文件是否设置了 sticky 属性
# -G FILE 判断文件属于有效的用户组
# -O FILE 判断文件属于有效的用户
# FILE1 -nt FILE2 FILE1 比 FILE2 新时返回真
# FILE1 -ot FILE2 FILE1 比 FILE2 旧时返回真
# 字符串测试
# 主要包括 等于、不等于、大于、小于、是否为空
# -z string 为空时返回真
echo "字符串测试"
[ -z "" ]
echo '[ -z "" ]' $? # 结果 0,表示为真
# -n string 非空时返回真
[ -n "aaa" ]
echo '[ -n "aaa" ]' $?
[ "string1" = "string2" ]
echo '[ "string1" = "string2" ]' $?
[ "string1" != "string2" ]
echo '[ "string1" != "string2" ]' $?
[ "string1" > "string2" ]
echo '[ "string1" > "string2" ]' $?
[ "string1" < "string2" ]
echo '[ "string1" < "string2" ]' $?
# 整数比较
# -eq 意 相等
# -gt 意 >
# -lt 意 <
# -ge 意 >=
# -le 意 <=
# -ne 意 !=
[ 1 -eq 2 ]
echo '[ 1 -eq 2 ]' $?
[ 1 -gt 2 ]
echo '[ 1 -gt 2 ]' $?
[ 1 -lt 2 ]
echo '[ 1 -lt 2 ]' $?
[ 1 -ge 2 ]
echo '[ 1 -ge 2 ]' $?
[ 1 -le 2 ]
echo '[ 1 -le 2 ]' $?
[ 1 -ne 2 ]
echo '[ 1 -ne 2 ]' $?
# 逻辑测试符与逻辑运算符
# ! expression 取反
# expression -a expression 同为真,结果为真
# expression -o expression 只有有一个为真,结果为真
touch tmp.sh
[ ! -e tmp.sh ]
echo '[ ! -e tmp.sh ]' $?
[ -e tmp.sh -a -e tmp1.sh ]
echo '[ -e tmp.sh -a -e tmp1.sh ]' $?
[ -e tmp.sh -o -e tmp1.sh ]
echo '[ -e tmp.sh -o -e tmp1.sh ]' $?
# -a -o 可以用 && 和 || 替代,不过写法上会有区别
[ -e tmp.sh ] && [ -e tmp1.sh ]
echo '[ -e tmp.sh ] && [ -e tmp1.sh ]' $?
[ -e tmp.sh ] || [ -e tmp1.sh ]
echo '[ -e tmp.sh ] || [ -e tmp1.sh ]' $?
rm tmp.sh
rm string2
判断
#!/bin/bash
# bash 的判断与循环与其他语言类似,有 if else elif case
# if 判断结构
# if expression; then
# command
# elif expression; then
# command
# else
# command
# fi
if [ ! -e tmp.sh ];
then
echo "tmp.sh 不存在,创建它"
touch tmp.sh
if [ -e tmp.sh ]; then
echo "tmp.sh 创建好了"
else
echo "tmp.sh 创建失败"
fi
else
echo "tmp.sh 存在,删了它"
rm tmp.sh
fi
# case 判断结构
# case VAR in
# var1) command ;;
# var2) command ;;
# ...
# *) command ;;
# esac
read -p "请输入数字:" NUM
case $NUM in
1) echo "输入为 1" ;;
2) echo "输入为 2" ;;
*) echo "输入为 其他" ;;
esac
rm tmp.sh
循环
#!/bin/bash
# 循环
# Shell 的循环主要有 for、while、until、select 几种
# for 循环
# 带列表的 for 循环:
# for VAR in (list)
# do
# command
# done
for NUMBER in 1 2 3 4 5
do
echo $NUMBER
done
fruits="apple banana orange"
for FRUIT in ${fruits}
do
echo $FRUIT
done
# 循环数字时可以使用 {a..b} 表示从 a 循环到 b
for N in {2..10}
do
echo $N
done
# 其中 {2..10} 可以用 seq 命令替换
echo "echo with seq:"
for N in $(seq 2 10)
do
echo $N
done
# seq 命令可以加 “步长”
for N in $(seq 1 2 20)
do
echo $N
done
# 可以看出,for in 后面的内容可以是任意命令的标准输出
# 比如,我们可以输出当前目录下的所有带 sh 的文件
for VAR in $(ls | grep sh)
do
echo $VAR
done
# 如果 for 后面没有 in ,则相当于是 in $@
# 你可以执行 bash 07_循环.sh a b c 试一试
for VAR
do
echo $VAR
done
# 类 C 的 for 循环
# for ((exp1; exp2; exp3))
# do
# command
# done
for ((i=0, j=100; i < 10; i ++))
do
echo $i $j
done
# while 循环
# 语法如下:
# while expression
# do
# command
# done
# while ((1)) 会无限循环
COUNT=0
while [ $COUNT -lt 5 ]
do
echo $COUNT
let "COUNT++"
done
# while 按行读取文件
echo "john 30 boy
sue 20 girl" > tmp.txt
while read LINE
do
NAME=`echo $LINE | awk '{print $1}'`
AGE=`echo $LINE | awk '{print $2}'`
SEX=`echo $LINE | awk '{print $3}'`
echo $NAME $AGE $SEX
done < tmp.txt # 输入重定向
rm tmp.txt
# until 循环
# until 与 while 类似,区别在于 until 判断为 否,会继续循环,而 while 判断为 真,才继续循环
# until ((0)) 会无限循环
COUNT=0
until [ $COUNT -gt 5 ]
do
echo $COUNT
let "COUNT++"
done
# select 循环
# select 是一种菜单式的循环方式,语法结构与 for 相似,每次循环的值由用户选择
echo "choose your menu:"
select MENU in "apple" "banana" "orange" "exit"
do
echo "you choose $MENU"
if [[ $MENU = "exit" ]]
then
break
else
echo "choose again"
fi
done
# 循环控制,break continue,与其他编程语言一致
函数
#!/bin/bash
# 函数
# 函数的定义
# function FUNCTION_NAME() {
# command
# }
# 省略 function 关键字
# FUNCTION_NAME() {
# command
# }
function func1 {
echo 1
}
func2() {
echo 2
}
# 函数调用
func1
func2
# 函数返回值
func3 () {
echo '请输入函数的返回值:'
read N
return $N
}
func3
echo "上个函数的返回值是" $? # 使用 $? 获取上一条指令的返回值
# 函数参数
# 与脚本的参数使用一致
func4 () {
echo "第一个参数 $1"
echo "第二个参数 $2"
echo "所有参数 $@"
echo "参数数量 $#"
}
func4 a b c
# 使用 set 可以指定位置的脚本(或函数)参数值
func5() {
set q w e
echo "参数1 $1"
echo "所有参数: $@"
}
func5
# 移动位置参数:在 Shell 中可以使用 shift 命令把参数左移一位
func6() {
while [ $# -gt 0 ]
do
echo current \$1 is $1
shift
done
}
func6 q w e r t
# 实现一个 pow 函数
pow() {
let "r=$1**$2"
return $r
}
pow 2 5
echo $?
重定向
#!/bin/bash
# 重定向
# 重定向是指将原本由标准输入输出的内容,改为输入输出的其他文件或设备
# 系统在启动一个进程时,会为该进程打开三个文件:
# 标准输入(stdin)、标准输出(stdout)、标准错误(stderr)
# 分别用文件标识符 0、1、2 标识
# 如果要为进程打开其他的输入输出,需要从证书 3 开始标识
# 默认情况下,标准输入为键盘,标准输出和标准错误为显示器
# 常见的 IO 重定向符号
# > 标准输出覆盖重定向,将命令的标准输出重定向到其他文件中,会直接覆盖原文件内容
# >> 标准输出追加重定向,将命令的标准输出重定向到其他文件中,不会覆盖文件,会在文件后面追加
# >& 标识输出重定向,讲一个标识的输出重定向到另一个标识的输入
# < 标准输入重定向,命名将从指定文件中读取输入,而不是从键盘中读取输入
# | 管道,从一个命令中读取输出,作为另一个命令的输入
# 输出重定向
# 把原本标准输出到屏幕的内容,重定向到 tmp.txt 文件中
echo "result1" > tmp.txt
cat tmp.txt
echo "result2" > tmp.txt
cat tmp.txt
# 输出追加
echo "输出追加:"
echo "result3" >> tmp.txt
echo "result3" >> tmp.txt
echo "result3" >> tmp.txt
cat tmp.txt
rm tmp.txt
# 标识输出重定向
echo "未重定向标准错误,会直接输出到页面"
# 制定一个不存在的命令
adhfafahdfakdf > tmp.txt
echo "tmp.txt:" `cat tmp.txt`
rm tmp.txt
echo "重定向标准错误到标准输出,会输出到文件中"
asiiaodfuoaf > tmp.txt 2>&1
echo "tmp.txt:" `cat tmp.txt`
# 标准输入重定向
echo "标准输入重定向:"
while read Line
do
echo $Line
done < tmp.txt
# 管道
# 获取 .sh 文件的名称
ls | grep .sh | cut -f1 -d'.'
# 使用 exec
# exec 是 Shell 的内建命令,执行这个命令时,系统不会启动新的 Shell,而是用被执行的命令替换当前的 Shell 进程
# 因此,在执行完 exec 的命令后,该 Shell 进程将会主动退出
# 例如:执行 exec ls ,后续的其他命令将不会执行。你也可以直接打开 Shell,执行 exec ls 试试
# 此外,exec 还可以用于 I/O 重定向。
# exec < file 将 file 文件中的内容作为 exec 的标准输入
# exec > file 将 file 文件作为标准输出
# exec 3<file 指定文件标识符
# exec 3<&- 关闭文件标识符
# exec 3>file 将写入文件标识符的内容写入到指定文件(输出重定向)
# exec 4<&3 创建文件标识符4,4是3的拷贝 (类似标识输出重定向 2>&1)
# 注:不同的 shell 环境可能会有所差别,比如我在 mac 的 zsh 下就不能正常使用 exec 重定向
# Here Document
# here doc 又称为 此处文档,用于在命令或脚本中按行输入文本。
# 格式为 command << delimiter
# delimiter 是用于标注结束的分隔符
# 示例:
# 你可以在命令行下输入 sort << END 试试
# 你可以在命令行下输入 cat > tmp.txt << END 试试
cat << EOF > tmp.txt
1
2
3
EOF
cat tmp.txt
rm tmp.txt
数组
#!/bin/bash
# 数组
# bash 只支持一维数组
# 定义数组
declare -a mArray
mArray[0]="nihao"
mArray[1]=2
# 定义时赋值,数组的元素用空格分开,其他字符会被当成值,比如 "php", 会被当成 php,
declare -a mArray=("php" "python" 123)
# 数组取值,需要用 ${数组名[索引]} 语法
echo ${mArray[0]}
echo ${mArray[1]}
echo ${mArray[2]}
# 使用 @ * 可以索引全部元素
# @ 得到以空格分开的元素值
# * 得到整个字符串
echo ${mArray[@]}
echo ${mArray[*]}
# 数组长度
echo "数组长度是 ${#mArray[@]}"
echo "数组长度是 ${#mArray[*]}"
# 数组截取
# 可以获取子数组,下面示例为获取数组的第 1、2 下标位置的元素
echo ${mArray[@]: 1:2}
# 可以获取数组中某个元素的若干字符,下面示例为获取数组中第二个元素的 从0开始 3个字符
echo ${mArray[1]: 0:3}
# 合并数组
Front=("javascript" "typescript") # 数组声明也可以忽略 declear -a
Conn=(${mArray[@]} ${Front[@]})
echo ${Conn[@]}
echo ${#Conn[@]} # 合并得到数组的长度
# 替换元素
mArray=(${mArray[@] /123/"java"})
echo ${mArray[@]}
# 取消数组或元素
unset mArray[1]
echo "取消下标为 1 的元素后,数组为:${mArray[@]},数组长度为 ${#mArray[@]}"
# 需要注意的是,数组的 1 位置的元素变为了空,而不是后面的元素向前移动
echo "数组 1 位置的元素为 ${mArray[1]}, 2 位置的元素为 ${mArray[2]}"
字符处理
#!/bin/bash
# 字符处理
# 管道
# 从一个命令中读取输出,作为另一个命令的输入
# 示例
# ls | grep .sh | cut -f1 -d'.'
# grep
# grep 是基于行的文本搜索工具,该命令常用的参数有:
# grep [-ivnc] '需要匹配的字符' 文件名
# -i 不区分大小写
# -c 统计包含匹配的行数
# -n 输出行号
# -v 反向匹配
# 其中 '需要匹配的字符' 支持正则表达式模式
grep -in 'func' 01_变量.sh
# sort
# sort 可以对无序的数据进行排序
# sort [-ntkr] 文件名
# -n 采取数字排序
# -t 指定分隔符
# -k 指定第几列
# -r 反向排序
# 示例 使用空格分开每行,按第二列进行排序
echo "3 1 3
1 2 4
5 3 2
1 2 4
5 3 4
2 3 4" | sort -t ' ' -k 2
# uniq
# 使用 uniq 可以删除重复内容
echo "123
123
ab
ab" | uniq
# cut 截取文本
# cut -f指定的列 -d'分隔符'
# 指定的列可以用逗号分隔开,或者使用范围
echo "jhon 10 boy class1
lili 12 girl class2" | cut -f2-4 -d ' '
# tr 做文本转换
# tr '原字符' '目标字符' 其中原字符与目标字符一一对应
head -n 5 01_变量.sh | tr '[a-z]' '[A-Z]'
# paste 进行文本合并
# paste 会把文本按行合并。
# paste -d
echo "1
2
3" > tmp1.txt
echo "a
b
c" > tmp2.txt
paste -d: tmp1.txt tmp2.txt > tmp.txt
cat tmp.txt
# split 分割大文件
# split -l lines file dist_file
# 示例
split -l 5 01_变量.sh split_file
ls | grep split_file
rm split_file*
# sed 与 awk
# ...
# 如果现有工具不能满足你对字符串处理的需求,那就去了解一下 sed 和 awk 命令。
rm tmp*
示例-操作数据库
#!/bin/bash
USER=root
PASSWORD=root
# 使用 -e 执行
databases=`mysql -u$USER -p$PASSWORD -e"show databases"`
for db in $databases
do
echo "Tables in $db:"
# 使用 here doc 执行代码块
mysql -u$USER -p$PASSWORD << EOF
use $db;
show tables;
EOF
# 也可以使用输入重定向
# mysql -u$USER 0pPASSWORD < select.sql
done
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。