Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言。
Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。
Ken Thompson 的 sh 是第一种 Unix Shell,Windows Explorer 是一个典型的图形界面 Shell。
Shell 脚本
Shell 脚本(shell script),是一种为 shell 编写的脚本程序。
业界所说的 shell 通常都是指 shell 脚本,但读者朋友要知道,shell 和 shell script 是两个不同的概念。
由于习惯的原因,简洁起见,本文出现的 "shell编程" 都是指 shell 脚本编程,不是指开发 shell 自身。
#!/bin/bash
touch hello.txt
# 变量类型默认都是字符串类型,无法直接进行数值运算
echo "脚本主题: data本身就是复数" >> hello.txt
echo "========================= 特殊变量 $n $# $* $@ ========================="
echo "$HOME"
echo "$SHELL"
echo "$USER"
# $1 获取第一个参数,$2 获取第二个参数
echo "第一个爱妃是 $1, 可谓是倾国倾城"
# $# 获取入参个数
echo "此次入宫了 $# 个妃子"
# $* 代表所有参数,看作整体
echo "妃子名单*: $*"
# $@ 代表所有参数,区分对待
# shellcheck disable=SC2145
echo "妃子名单@: $@"
# $? 上一次命令的执行状态。0: 正确执行; 非0 : 执行发生错误(具体是哪个数,由命令自己决定)
echo " 上一次命令执行状态: $? "
echo "========================= 定义变量(随用随注销) ========================="
echo "A=1, 等号两边不能有空格"
echo "变量名可以由字母、数字和下划线组成,但是不能以数字开头"
echo "变量默认都是以字符串类型,无法直接进行数值运算"
echo "变量的值如果有空格,需要使用双引号或单引号括起来"
Z=1
echo "$A的值是: $Z"
echo "========================= 运算符 ========================="
echo "一共有三种方式,推荐使用$((运算符形式))"
echo " 1. $((8 + 8 * 8))"
# shellcheck disable=SC2007
echo " 2. $[8 + 8 * 8]"
# expr
echo " 3. expr 8 + 8 * 8"
echo "注意: 和声明变量不同,expr运算符间要有空格, 乘号还要转义 /*, 而且还不能运算两个以上的数字"
echo "========================= if条件判断 ========================="
echo "基本语法 [ condition ] (注意 condition 前后要有空格)"
echo "注意: condition可以是字符串: 条件非空即为true, [ wyf ] 返回true, [] 返回false"
echo " 也可以是条件表达式"
echo "注意: 在命令行中可以查询$?, 为0,代表是true, 但是在sh脚本中不管是执行了true和false分支,都是0"
echo "========================= (1) 字符串比较 ========================="
echo "= 字符串比较"
# shellcheck disable=SC2034
str="hello"
if [ "$str" ]; then
echo "true: 字符串{$str}不为空"
else
echo "false: 字符串{$str}为空"
fi
echo "========================= (2) 两个整数之间的比较 ==================="
echo "-lt 小于(less than) -le 小于等于(less equal)"
echo "-eq 等于(equal) -ne 不等于(not equal) "
echo "-gt 大于(greater than) -ge 大于等于(greater equal)"
# shellcheck disable=SC2050
if [ 23 -ge 22 ]; then
echo "true: 23 >= 22"
else
echo "false: 23 < 22"
fi
echo "========================= (3) 按照文件权限进行判断 ==================="
echo "-r 有读的权限(read)"
echo "-w 有写的权限(write)"
echo "-x 有执行的权限(execute)"
echo "========================= (4) 按照文件类型进行判断 ==================="
echo "-e 文件存在(exist)"
echo "-f 文件存在并且是一个常规的文件(file)"
echo "-d 文件存在并且是一个目录(directory)"
if [ -d hello.sh ]; then
echo "true: hello.sh 是directory"
else
echo "false: hello.sh 不是directory"
fi
echo "========================= 流程控制(重点) ==================="
echo "========================= (1) if判断 ==================="
echo "基本语法: if 后要有空格和fi闭环"
echo "if [ condition ]; then"
echo " true分支 "
echo "fi"
echo "====== elif 和 else ======"
echo "
if [ condition ]; then
commands;
elif [ condition ]; then
commands;
else
commands;
fi"
echo "========================== (2) case语句 ========================"
# shellcheck disable=SC2162
# read -p "please input a number to case statement:" num #打印信息提示用户输入,输入信息赋值给num变量
num=3
case "$num" in
1)
echo "The num you input is 1"
;;
[2-5])
echo "The num you input is 2-5"
;;
[6-9])
echo "The num you input is 6-9"
;;
*)
echo "please input number[1-9] int"
esac
echo "========================== (3) for循环 ========================"
sum=0
for ((i = 1; i <= 100; i++))
do
sum=$((sum + i))
done
echo "1~100的累加和: $sum"
echo "遍历输入参数: "
for p in "$@"
do
echo "爱妃: $p"
done
sum2=0
k=1
while [ $k -le 50 ]
do
sum2=$((sum2 + k))
k=$((k + 1))
done
echo "1~50的累加和: $sum2"
#echo "========================== (3) read读取控制台输入 ========================"
#read -r -p "请输入你喜欢的演员, -r可以正常读取反斜杠: " girlName
#echo "$girlName 是你的女朋友"
#read -r -t 3 -p "请在三秒内输入另一个女明星: " other
#
#if [ "$other" ]; then
# echo "$other 也是你的女朋友"
#else
# echo "我就知道你很专一"
#fi
echo "========================== 系统函数 ========================"
echo "========================== (1) basename ========================"
# 基本语法: basename NAME [SUFFIX]
# 删除前导目录, 打印名称
# 如果指定,还可以删除后缀
# $(cmd) 指定输出
fileNameWithSuffix=$(basename include/stdio.h)
echo "$fileNameWithSuffix" # stdio.h
fileName=$(basename include/stdio.h .h)
echo "$fileName" # stdio
echo "========================== (2) dirname ========================"
# Usage: dirname [OPTION] NAME...
# 输出 Name的所在最后一个斜杠之前的内容(NAME所在目录部分,且删除最后一个斜杠)
# 如果Name不包含斜杆,则输入 '.' ,代表当前目录
dir1=$(dirname /usr/bin/)
dir2=$(dirname /AuI18N/2052/usr/bin/a.txt)
dir3=$(dirname stdio.h)
echo "dir1: $dir1"
echo "dir2: $dir2"
echo "dir3: $dir3"
echo "========================== 自定义函数 ========================"
echo "标准写法: "
function name() {
echo "== name() =="
return 0
# [return value], value取值范围是0-255
# 表示函数的返回值,可写可不写
}
name
# 函数返回值只能通过$?系统变量获得,可以显示加。如果不加,讲以最后一条命令运行结果做回返回值
echo "func result : $?"
function twoSum() {
s=0
s=$(($1+$2))
echo "twoSum result : $s"
}
# 调用 Shell 函数时可以给它传递参数,也可以不传递
# 如果不传递参数,直接给出函数名字即可:name
# 如果传递参数,那么多个参数之间以空格分隔:name param1 param2 param3
twoSum 6 6
echo "========================== sed ========================"
echo "Usage: sed [选项] 'command' fileName"
echo " -n, --quiet, --silent 只打印匹配行"
echo "也可以用数字开头,代表对指定行操作"
echo " 常用命令: s替换 d删除 p打印 q退出 i行前 a行后"
echo "-e 和 ; 都可以实现多条cmd"
#sed "s/u/UC/g" abc.txt # u ==> UC, g代表整行都替换
#echo "=="
#sed "s/u/UC/" abc.txt
# sed "s/a//g" abc.txt # 把所有的a都删掉了
# sed "s/#.*//g" abc.txt # -i 会对源文件进行修改
# sed "s/\s*#.*//g" abc.txt # \s*代表0或多个空格
# sed "s/\s*#.*//g; /^$/d" abc.txt # /^$/d删除空行
# sed "/a/q" abc.txt # 遇到第一个a就退出
# sed -n "/a/p" abc.txt # 打印匹配到a的行
# sed "3q" abc.txt # 打印到第三行就退出
# sed "3p" abc.txt # 到第三行就打印
#sed "/^wo/a cao" abc.txt # a 在匹配行后加一行
#sed "/^wo/i ai~" abc.txt # i 在匹配行前加一行
#cmd1="2a mei nv22"
#sed "$cmd1" abc.txt
#sed "3s/a/TT/g" abc.txt # 只给第三行的a换成了TT
#sed "/lai/d" abc.txt
#sed "4d;s/wo/ni/g" abc.txt # ; 号实现多条命令,删除第四行,且替换字符
#sed -e "4d" -e "s/wo/ni/g" abc.txt # ; 号实现多条命令,删除第四行,且替换字符
echo "============================== awk ============================="
echo "常用函数:
tolower():字符转为小写。
length():返回字符串长度。
substr():返回子字符串。
rand():随机数。
"
echo "一个强大的文本分析工具,把文件逐行读入,以空格为默认分隔符将每行切片,切开的部分再进行分析处理"
echo "awk [选项参数] 'pattern1{action1} pattern2{action2}...'" fileName
echo "pattern : 表示awk在数据中查找的内容"
echo "action : 在找到所匹配内容时所执行的一系列命令"
echo "常用选项参数 -F 指定分隔符"
echo "常用选项参数 -v 定义变量, awk的变量是与世隔绝的,里面取不到外面的,外面的也取不到里面的"
awk -F ":" '/^root/ {print $7}' demo.txt
awk -F ":" '/^root/ {print $1 ", " $7}' demo.txt
awk -F ":" '/^root/ {printf("第一列内容是%s, 第7列内容是: %s ! \n", $1, $7)}' demo.txt
# BEGIN 和 END
awk -F ":" 'BEGIN{print "=== lu ben wei niu bi~ ==="}
/^root/ {printf("第一列内容是%s, 第7列内容是: %s ! \n", $1, $7)}
END{print "=== bi xv di ! ==="}' demo.txt
awk -F ":" 'BEGIN{print "=== lu ben wei niu bi~ ==="}
{printf("第一列内容是%s, 第7列内容是: %s ! \n", $1, $7)}
END{print "=== bi xv di ! ==="}' demo.txt
awk -F ":" -v i=1 '{print $3 + i}' demo.txt # 定义变量,给第三列加1
# awk 内置变量
awk -F ":" '{printf("文件名: %s, 行号: %s, 该行一共 %s 列, 第一列: %s \n", FILENAME, NR, NF, $1)}' demo.txt
# 打印空行的行号
awk '/^$/ {printf("空行行号: %s \n", NR)}' demo.txt
echo "================== wc ================="
echo "Usage: wc [OPTION]... [FILE]..."
echo "wc 会把文件名称打印出来,可以用awk去除"
total_lines=$(wc -l demo.txt | awk -F " " '{print $1}')
total_bytes=$(wc -c demo.txt | awk -F " " '{print $1}')
echo "demo.txt ==>> 总行数: $total_lines, 总字节数: $total_bytes"
echo "================ sort =================="
echo "Usage: sort [OPTION]... [FILE]..."
echo "
-n, 按照数值排序
-r, 反序
-b, 忽略前导空格
-t, 指定排序时所用的栏位分隔字符
-k, 指定排序的列
"
echo "按照日排序(第三列)"
sort -t "-" -k 3 -n sort.txt
sort -t "-" -k 3 -n -r sort.txt
常见问题:
#!/bin/bash
# 计算demo.txt第三列总和
third_sum=$(awk -F ":" -v sum=0 '{sum+=$3} END{print sum}' demo.txt)
echo "第三列的和是: $third_sum"
unset third_sum
# 判断一个文件是否存在
if [ -f sort.txt ]; then
echo "sort.txt 文件存在"
else
echo "sort.txt 文件不存在"
fi
# 在当前目录查找包含字符 shen 的文件名称
# uniq 去重
# uniq 在后面显示行重复次数
grep -r shen | awk -F ":" '{print $1}' | uniq -c
# 输出 0 ~ 500 之间7的倍数
#for ((i = 0; i <= 500; i++))
#do
# if [ $((i % 7)) -eq 0 ]; then
# echo "$i"
# fi
#done
#unset "$i"
#exit 0
#for i in {0..500}
#do
# if [ $((i % 7)) -eq 0 ]; then
# echo "$i"
# fi
#done
#unset "$i"
#exit 0
# 生成 0 ~ 500的数字, 步长为7
# seq 0 7 500
# 写一个 bash脚本以统计一个文本文件 nowcoder.txt中字母数小于8的单词。
#
#
#
#示例:
#假设 nowcoder.txt 内容如下:
#how they are implemented and applied in computer
#
#你的脚本应当输出:
#how
#they
#are
#and
#applied
#in
awk '{
for (i=1; i <= NF; i++) {
if (length($i) < 8){
print $i
}
}
}' n.txt
# 打印出空行,或者只有空格和制表符的行
awk 'NF == 0 {print NR}' n.txt
# 统计每个单词出现的次数
echo "===!!@@====="
awk '{
for (i=1; i <= NF; i++) {
print $i
}
}' now.txt | sort | uniq -c | awk '{printf("%s %s\n", $2, $1)}' | sort -k 2 -n > res.txt
echo "====="
awk '{
for (i = 1; i <= NF; ++i)
mp[$i]++;
}
END {
for (k in mp)
printf("%s %d\n", k, mp[k]);
}' now.txt | sort -k 1 -n
awk '{print $2}' nowcoder.txt |
sort -n |
uniq -c |
awk '$1 > 1 {print $0}'
echo "
假设每行列数相同,并且每个字段由空格分隔
示例:
假设 nowcoder.txt 内容如下:
job salary
c++ 13
java 14
php 12
你的脚本应当输出(以词频升序排列):
job c++ java php
salary 13 14 12
"
cols=$(sed "1q" nowcoder.txt | awk '{print NF}')
for ((i = 1; i <= cols; i++)); do
awk -v k="$i" '{printf("%s ", $k)}' nowcoder.txt
echo
done
echo "
写一个 bash脚本以统计一个文本文件 nowcoder.txt中每一行出现的1,2,3,4,5数字个数
并且要计算一下整个文档中一共出现了几个1,2,3,4,5数字数字总数。
示例:
假设 nowcoder.txt 内容如下:
a12b8
10ccc
2521abc
9asf
你的脚本应当输出:
line1 number: 2
line2 number: 1
line3 number: 4
line4 number: 0
sum is 7
"
awk -F "" '
BEGIN{sum = 0}
{
count = 0;
for (i = 0; i < NF; i++) {
if ($i == 1 || $i == 2 || $i == 3 || $i == 4 || $i == 5) {
count++;
}
}
sum += count;
printf("line%s number:%d\n", NR, count);
}
END{printf("sum is %d\n", sum)}
' nowcoder.txt
echo "
写一个bash脚本以实现一个需求,求输入的一个的数组的平均值
第1行为输入的数组长度N
第2~N行为数组的元素,如以下为:
数组长度为4,数组元素为1 2 9 8
示例:
4
1
2
9
8
那么平均值为:5.000(保留小数点后面3位)
你的脚本获取以上输入应当输出:
5.000
"
awk 'BEGIN{sum = 0; len = 0}
{
if (NR == 1) {
len = $1;
} else {
sum += $1;
}
}
END{printf("%0.3f\n", sum / len)}'
xargs 应用场景1: 批量更改当前目录下的文件名
ls ./ | xargs -I GG echo "mv GG prefix_GG"
ls ./ | xargs -I GG mv GG prefix_GG
也可以这样批量更改该文件下的文件名
for i in ./*; do
name=$(basename "$i")
echo "mv $name sup_$name"
done