一、Shell的基础语法
1、循环语句
1)for循环
for 变量 in (list); do
// 编写执行的语句
done
#!/bin/bash
for i in 1 2 3 4 5;do
echo $i
done
for i in {1..100};do
if [ $[ i % 10 ] -eq 0 ]; then
echo $i
fi
done
for i in {1..100};do
if (( (i % 10) == 0 )); then
echo $i
fi
done
for i in {1..100..2};do
echo "奇数为:$i"
done
for file in `ls /home/scripts`;do
echo $file
done
2) while循环
while 条件; do
执行语句
done
// 死循环
while true; do
执行语句
done
1~100之间相加
#!/bin/bash
i=1
sum=0
while [ $i -le 100 ]; do
sum=$[ sum + i]
((i++))
done
echo $sum
// 每隔一秒钟打印一个当前时间
#!/bin/bash
while true; do
date
sleep 1
done
2、分支语句中的switch
语法
case 值 in
匹配的值1)
执行语句
;;
匹配的值2)
执行语句
;;
...
esac
案例:
switch 写法其实是if 写法的一个 语法糖(有他没他都无所谓)
在控制台输入数据 1~7,如果是1 打印星期一
#!/bin/bash
echo "请输入1~7之间的数字"
read num
case $num in
1) echo "星期一"
;;
2) echo "星期二"
;;
3) echo "星期三"
;;
4) echo "星期四"
;;
5) echo "星期五"
;;
6) echo "星期六"
;;
7) echo "星期日"
;;
esac
思考:
假如输入了一个值8 怎么办?
假如输入的不是数字,怎么办?
如果使用if如何编写呢?
3、跳出循环(break contiune)
break 跳出所有的循环
continue 跳出本次循环
每隔1s打印时间,当打印到第10次的时候,跳出该循环
#!/bin/bash
i=1
while true; do
date
sleep 1
if [ $i -eq 10 ];then
break
fi
((i++))
done
continue 跳出本次循环,进入下一个循环
打印1~100之间不是10的倍数的数字
#!/bin/bash
for i in {1..100}; do
if test $[ i % 10 ] -eq 0; then
continue
fi
echo $i
done
4、函数(类似于java中的方法)
function 方法名()
{
//执行语句
[return xxx;]
}
快速定义个方法,然后执行
#!/bin/bash
function showName()
{
echo "该方法被执行了"
}
showName
如何定义有参数的方法:
function showName2()
{
echo "打印参数1 $1"
echo "参数二是:$2"
}
showName2 1 100 1000
方法可以有返回值:
参数的返回: return 返回值
写一个案例,了解一下
#!/bin/bash
function showName(){
echo "你调用了这个方法"
echo "第一个参数$1"
echo "第二个参数$2"
return $[ $1 + $2 ]
}
showName 110 119
val=$?
echo $val
风凉话:假如两个值的结果大于了255,也不准了
想要返回值是字符串,做梦,可以通过打印来解决,一般没有返回值的话,返回最后的输出结果。
function validateStr(){
echo "abc"
}
vvv=`validateStr`
echo $vvv
5、数组
bash中,只支持一维数组,不支持多维数据。
数组的名字=(val1 val2 val3 ...)
my_arr=(a b c d "E") 定义数组,并且赋值。
//直接通过下标赋值
my_arr2[0]=hello
my_arr2[1]=world
my_arr2[2]=java
数组的循环:
第一种方式类似于:foreach
#!/bin/bash
my_arr=(AA BB CC)
for str in ${my_arr[*]}
do
echo $str
done
第二种使用下标的方式
#!/bin/bash
my_arr=(AA BB CC)
length=${#my_arr[*]}
for((i=0;i<length;i++));
do
echo ${my_arr[$i]}
done
合并起来的一个脚本是:
#!/bin/bash
echo "使用第一种方式循环:"
my_arr=(AA BB CC)
for str in ${my_arr[*]}
do
echo $str
done
echo "使用第二种方式循环:"
length=${#my_arr[*]}
for((i=0;i<length;i++));
do
echo ${my_arr[$i]}
done
6、脚本文件相互加载
类似于之前java语言中的 import
a.sh 中定义了一个数组,b.sh中直接使用
a.sh中直接定义个数组
#!/bin/bash
echo "我是a脚本文件:"
my_arr=(AA BB CC)
b.sh中使用a.sh中的数组(使用之前要加载,类似于java中的导包)
#!/bin/bash
source ./a.sh #加载文件中的内容
for str in ${my_arr[*]}
do
echo $str
done
7、一个脚本
每次都要创建一个新的文件,比较麻烦,而且每次都要写 #!/bin/bash 这句话
#!/bin/bash
if [ -e $1 ]; then
read -p "文件已存在,是否覆盖(Y/y),任意键取消" ori
if [[ $ori == 'y' || $ori == 'Y' ]]; then
touch $1
echo "#!/bin/bash" > $1
chmod +x $1
fi
else
touch $1
echo "#!/bin/bash" > $1
chmod +x $1
fi
将这个脚本放在如下位置:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
也可以使用vim编辑器完成上述功能:
安装vim编辑器:
yum install -y vim
在/etc/vimrc文件结尾添加
"#给sh文件添加头部注释
autocmd BufNewFile *.sh exec ":call WESTOS()"
function WESTOS()
call append(0,"#!/bin/bash")
endfunction
autocmd BufNewfile * normal G
创建一个文件,自动添加 #!/bin/bash
vim 15.sh
二、shell脚本的高级部分
grep 、 awk 、sed 号称是shell编程的三剑客
1、cut --提取,从命令结果中提取对应的内容
准备数据 1.txt
111:aaa:bbb:ccc
222:ddd:eee:fff
333:ggg:hhh
444:iii
cut 后面的 -c 的意思是按照字符选取内容
范围控制
1、提取1.txt中前两行的第五个字符
head -2 1.txt // 查看文件的前两行
head -2 1.txt | cut -c 5
cut 本身也可以直接提取内容
cut -c 5 1.txt
2、截取1.txt文件中前两行以:进行分割的1,2,3段内容
head -2 1.txt | cut -d ':' -f 1,2,3
head -2 1.txt | cut -d ':' -f 1-3
2、sort 排序
准备数据:2.txt
banana
apple
pear
orange
pear
1、去重数据
将 1.txt 中 第二列数据,倒序排列
[root@bigdata01 datas]# cut -d ":" -f 2 1.txt | sort -r
iii
ggg
ddd
aaa
2、数值类型操作
对数值类型进行的操作
准备数据:3.txt
1
3
5
7
11
2
4
6
10
8
9
倒序:
造数据: 4.txt
zhangsan 68 99 26
lisi 98 66 96
wangwu 38 33 86
zhaoliu 78 44 36
maqi 88 22 66
zhouba 98 44 46
需求:根据第二个成绩进行倒序排序
cat 4.txt | sort -t ' ' -k 3
3、wc (wordcount)--单词统计
wc 跟上文件名 显示文件的字节数,单词数,文件的行数
造数据:5.txt
111
222 bbb
333 aaa bbb
444 aaa bbb ccc
555 aaa bbb ccc ddd
666 aaa bbb ccc ddd eee
wc -l 5.txt
wc -l 1.txt 2.txt 3.txt 4.txt 5.txt
wc -l *.txt
我想看一下某个文件夹下有多少个文件
ls /etc | wc -w
ls /etc | wc -l
4、awk(重点)
awk可以实现模糊查询,按需截取字符串,进行判断以及简单的运算
模糊查询 '张三' ‘张三三'
cat 4.txt | awk '/zhang|li/'
awk '/zhang|li/' 4.txt
2、指定分隔符,按照下标显示内容
打印4.txt 中每个学生的姓名以及前两门的成绩
cat 4.txt | awk -F ' ' '{print $1,$2,$3}'
实战:
cat 4.txt | awk -F ' ' '{print toupper($1)}'
awk中还可以添加条件判断
cat 4.txt | awk -F ' ' '{if($4>60) print $1,$4}'
cat 4.txt | awk -F ' ' '{if($4>60) print $1,$4,"及格";else print $1,$4,"不及格"}'
awk -F ' ' '{if($2>=60) print "及格",$1,$2;else print "不及格",$1,$2}' 4.txt
//求最后一个成绩的总分
cat 4.txt | awk -F ' ' 'BEGIN{print "开始计算成绩总和"}{total=total+$4}END{print total}'
它是由三部分组成的
BEGIN{} 这一部分只执行一次
END{} 该代码块中的语句也只执行一次
中间{} 每读取一行数据,就执行一次
BEGIN 和 END 可以选择性的使用
// 获取记录条数:
cat 4.txt | awk -F ' ' 'BEGIN{print "开始计算成绩总和"}{total=total+$4}END{print total,NR}'
// 获取平均分
cat 4.txt | awk -F ' ' 'BEGIN{print "开始计算成绩总和"}{total=total+$4}END{print total,NR,(total/NR)}'
awk -F ' ' 'BEGIN{print "开始计算最后一个学科的总成绩"}{total=total+$4;print NR}END{print total/NR}' 4.txt
开始计算最后一个学科的总成绩
1
2
3
4
5
6
59.3333
[root@bigdata01 datas]# awk -F ' ' 'BEGIN{print "开始计算最后一个学科的总成绩"}{total=total+$4;print $0,NF,$NF}END{print total/NR}' 4.txt
开始计算最后一个学科的总成绩
zhangsan 68 99 26 4 26
lisi 98 66 96 4 96
wangwu 38 33 86 4 86
zhaoliu 78 44 36 4 36
maqi 88 22 66 4 66
zhouba 98 44 46 4 46
59.3333
$0 表示当前行的内容
NF 表示当天行有多少个字段
$NF 表示当天行的最后一个字段
突发奇想: grep 中有一个参数,叫做 -v 代表取反
[root@bigdata01 datas]# grep -n zhangsan 4.txt
1:zhangsan 68 99 26
[root@bigdata01 datas]# grep -n zhangsan -v 4.txt
2:lisi 98 66 96
3:wangwu 38 33 86
4:zhaoliu 78 44 36
5:maqi 88 22 66
6:zhouba 98 44 46
[root@bigdata01 datas]# ps -ef| grep mysql
mysql 60707 1 0 03:23 ? 00:06:19 /usr/sbin/mysqld
root 105006 130555 0 19:36 pts/0 00:00:00 grep --color=auto mysql
[root@bigdata01 datas]# ps -ef| grep mysql | grep -v grep
mysql 60707 1 0 03:23 ? 00:06:20 /usr/sbin/mysqld
5、sed操作 --实现过滤和替换
2、搞一些数据 6.txt
aaa java root
bbb hello
ccc rt
ddd root nologin
eee rtt
fff ROOT nologin
ggg rttt
3、列出6.txt中的3~5行的数据
cat 6.txt | sed -n -e '3,5p'
显示第一行到最后1行的数据:
cat 6.txt | sed -n -e '1,$p'
打印第三行到第五航的数据,显示行号
一种写法,没有使用sed ,而是使用了cat -n
cat -n 6.txt|sed -n -e '3,5p'
另一种写法:
sed -n -e '3,5=' -e '3,5p' 6.txt
sed进行查找:
// 需求是查找每一行中包含login的数据
cat 6.txt | sed -n -e '/login/p'
cat 6.txt| grep login
不区分大小写的查找,使用 I 参数
Sed 中可以使用正则表达式:
cat 6.txt|sed -n -r -e '/r+t/p'
-r 后面可以跟正则表达式 r+ 表示 r 可以出现一次到多次 r后面必须跟上t
思考: 在这个里面如何写一个正则表达式,表示以r开头,以t结尾
Sed 进行删除操作:
先学习一个新命令 nl 可以查看文件,该文件自动添加行号
选项使用d 进行删除
显示除了3到5行的所有数据:
nl 6.txt | sed -e '3,5d'
nl 6.txt | sed -e '3,$d' // 只显示前两行数据了
[root@bigdata01 datas]# cat 6.txt | sed -e '3,$d' | cut -d ' ' -f 2
java
hello
1、在6.txt的第一行前面插入 xxxxxxx,并显示行号
nl 6.txt | sed -e '1i xxxxxxxx'
2、在6.txt的第二行后面插入 SSSSSSS,并显示行号
nl 6.txt | sed -e '2a SSSSSSSS'
把6.txt中的nologin替换成为huawei,并显示行号
cat 6.txt | sed -e 's/nologin/huawei/' // 按照字符串进行替换
cat 6.txt | sed -e '3c laoyan' // 按照行进行替换
以上的替换都是没有修改原来的数据的,sed也可以直接对原数据进行直接更改。
直接更改数据,首先数据进行备份
cp 6.txt 7.txt
sed -i -e 's/nologin/huawei/' 7.txt
sed -i -e '2c laoyanlaoyan' 7.txt
sed -i -e '1,2d' 7.txt // 真删除数据
Sed综合练习:获取本机的IP地址
ifconfig 在 linux上可以获取本机的IP信息
ipconfig 在windows上可以获取IP地址信息
因为我们使用的是mini版,没有这个服务:
yum search ifconfig
yum install -y net-tools.x86_64
安装完毕就可以使用ifconfig 这个服务了。
需求是:通过ifconfig 命令获取我的IP地址
ifconfig ens33 | grep 'inet ' | sed -e 's/inet //' | sed -e 's/ netmask.*//'
也可以这么写: \s 表示空格 * 表示0次到多次
ifconfig ens33 | grep 'inet ' | sed -e 's/\s*inet //' | sed -e 's/\s*netmask.*//'
思考:
ip addr 获取ip地址,怎么写?
ip addr | grep ens33 |grep 'inet' | sed -e 's/\s*inet //' | sed -e 's/\/.*//'
案例补充:
查找以d开头的内容:
[root@bigdata01 datas]# sed -n -e '/^d/p' 6.txt
ddd root nologin
以p开头的行前加[TAB]:注意此时的文件名字叫a
tab 键 是缩进的,比 空格 要大
$ cat a
pa:11:a
sa:32:c
app:5:b
stort:1:d
pear:4:aa
hello:3:f
$ sed '/^p/s/^/\t/' a
pa:11:a
sa:32:c
app:5:b
stort:1:d
pear:4:aa
hello:3:f
删除以a开头的行,(那么下面的输出,以a开头的行就没了)
$ sed '/^a/d' a
pa:11:a
sa:32:c
stort:1:d
pear:4:aa
hello:3:f
反向匹配(文件a中,输出只保留了a开头的行):
$ sed '/^a/!d' a
app:5:b
6、split 文件切割
创造数据:拷贝一个/etc/services
cp /etc/services $PWD
mv services big.txt
ll 查看文件的大小
按照字节进行切割:
split -b 100k big.txt
split -l 3000 big.txt
查看大小:
du -h /home/scripts/datas/xaa ==100k
删除所有的 x开头的文件
rm -rf ./x*
查看一个文件中有多少行
wc -l xaa
7、tr 替换和删除
tr 被替换的字符 新字符 是translate 的缩写
搞点数据:8.txt
laoyan
HELLO
abc12def34g
替换:
cat 8.txt | tr '[a-z]' '[A-Z]'
删除操作:
使用tr 进行一个单词计数的练习
9.txt
hello,world,hadoop
hive,sqoop,flume,hello
kitty,tom,jerry,world
hadoop
将单词中的,替换为换行符
cat 9.txt | tr ',' '\n'
//接着使用排序,去重等操作
cat 9.txt | tr ',' '\n' | sort | uniq
// 接着可以进行单词重复记录的数据
cat 9.txt | tr ',' '\n' | sort | uniq -c
8、uniq 去重
uniq 命令用于检查及删除文本文件中重复出现的行,一般与 sort 命令结合使用
造一些数据10.txt
张三 98
李四 100
王五 90
赵六 95
麻七 70
李四 100
王五 90
赵六 95
麻七 70
cat 10.txt | sort | uniq
9、tee 可以将数据输送到各个文件中 ,跟一个水管一样
cat 10.txt | sort | uniq -c | tee a.txt b.txt c.txt
查看一个文件夹中所有文件的大小:
du -h *
或者
du -h -a
三、周末作业
题目1:
编写一个 Shell 脚本,用于输出一个给定目录下的所有文件和子目录的名称。
#!/bin/bash
dir_path=/path/to/directory
for item in $(ls "$dir_path")
do
echo $item
done
另一种写法:
#!/bin/bash
echo "编写一个 Shell 脚本,用于输出一个给定目录下的所有文件和子目录的名称"
echo "请输入你的目录:"
read filedir
for file in `ls $filedir`;do
echo $file
done
优化:先判断一个路径是否存在
假如你想判断一个文件是否存在 使用 -e
假如你想判断一个文件夹是否存在,使用 -d
#!/bin/bash
echo "编写一个 Shell 脚本,用于输出一个给定目录下的所有文件和子目录的名称"
echo "请输入你的目录:"
read filedir
if [ -d "$filedir" ]; then
for file in `ls $filedir`;do
echo $file
done
else
echo "该路径不存在,请重新输入"
fi
题目2:
编写一个 Shell 脚本,用于将一个给定的整数乘以 2。
#!/bin/bash
num=5
result=$((num * 2))
echo "Result: $result"
题目3:
编写一个 Shell 脚本,用于输出一个给定目录下的所有 .txt 文件的名称。
#!/bin/bash
echo "输出一个给定目录下的所有 .txt 文件的名称:"
echo "请输入你的目录:"
read filedir
if [ -d "$filedir" ]; then
for file in `ls $filedir | grep .txt`;do
echo $file
done
else
echo "该路径不存在,请重新输入"
fi
也可以使用这个命令:
find /home/scripts/datas/ -name '*.txt'
题目4:
编写一个 Shell 脚本,用于计算一个给定整数的阶乘。
阶乘: 3!=3 * 2 * 1
3!= 3 * 2!
#!/bin/bash
function factorial(){
num=$1
if [ $num -eq 1 ];then
echo 1
else
echo $[ num * $(factorial $[ num - 1 ] ) ]
fi
}
read -p "请输入一个正整数:" num
result=`factorial $num`
echo $result
另一种写法:
#!/bin/bash
read -p "请输入一个整数:" int
acc=1
for ((i=int;i>0;i--));
do
acc=$[ acc * i ]
done
echo $acc
如何判断一个数是正整数?
if [[ $num =~ ^[1-9][0-9]+$ ]]
then
echo "是数字!"
else
echo "不是数字!"
fi
使用正则表达式进行判断
题目5:
编写一个 Shell 脚本,用于计算斐波那契数列的前 10 个数。
0 == 0
1 == 1
2 == 1
3 == 2
4 == 3
兔子队列: 0 1 1 2 3 5 8 13....
#!/bin/bash
#!/bin/bash
fibonacci() {
if [ $1 -eq 0 ]
then
echo 0
elif [ $1 -eq 1 ]
then
echo 1
else
echo $(( $(fibonacci $(( $1 - 1 ))) + $(fibonacci $(( $1 - 2 ))) ))
fi
}
for i in {0..9};do
echo `fibonacci $i`
done
题目6:
编写一个 Shell 脚本,用于判断一个给定的整数是否为偶数。
#!/bin/bash
is_even() {
if [ $(( $1 % 2 )) -eq 0 ]
then
echo "true"
else
echo "false"
fi
}
read -p "请输入一个数字:" num
if [ `is_even $num` == 'true' ];then
echo "$num 是一个偶数"
else
echo "$num 是奇数"
fi
题目7:
编写一个 Shell 脚本,用于将一个给定的字符串反转。
#!/bin/bash
reverse_string(){
str=$1
reversed=""
for((i=${#str}-1;i>=0;i--));do
reversed="$reversed${str:$i:1}"
done
echo $reversed
}
stra="hello world"
result=`reverse_string "$stra"`
echo $result
四、作业
题目1:
编写一个 Shell 脚本,用于计算一个目录下所有文件的总大小,并输出结果。
#!/bin/bash
total_size=0
for file in $(ls -A)
do
if [ -f "$file" ]
then
size=$(du -b "$file" | awk '{print $1}')
total_size=$((total_size + size))
fi
done
echo "该文件夹下的文件大小为: $total_size bytes"
题目2:
编写一个 Shell 脚本,用于批量重命名一个目录下的所有文件,将文件名中的空格替换为下划线。
#!/bin/bash
for file in $(ls -A)
do
if [ -f "$file" ]
then
new_name=$(echo "$file" | tr ' ' '_')
mv "$file" "$new_name"
fi
done
echo "All files renamed successfully"
题目3:
编写一个 Shell 脚本,用于将一个目录下所有的 .txt 文件复制到另一个目录中。
#!/bin/bash
source_dir=/path/to/source/dir
dest_dir=/path/to/dest/dir
if [ ! -d "$dest_dir" ]
then
mkdir "$dest_dir"
fi
for file in $(ls "$source_dir")
do
if [ "${file##*.}" = "txt" ]
then
cp "$source_dir/$file" "$dest_dir"
fi
done
echo "All .txt files copied successfully"
题目4:
编写一个 Shell 脚本,用于在一个目录下查找所有包含指定字符串的文件,并输出它们的文件名和行号。
#!/bin/bash
search_dir=/path/to/search/dir
search_string="example"
for file in $(grep -rl "$search_string" "$search_dir")
do
echo "Found in $file:"
grep -n "$search_string" "$file"
done
题目5:
编写一个 Shell 脚本,用于输出一个给定目录下的所有文件的行数。
#!/bin/bash
dir_path=/path/to/directory
for file in $(ls "$dir_path")
do
if [ -f "$dir_path/$file" ]
then
line_count=$(wc -l < "$dir_path/$file")
echo "$file: $line_count lines"
fi
done
假如你理解的题意:是将一个文件夹下所有的文件的行数累加 ,就通过循环,累加
假如你理解的是:每一个文件的行数和文件名 ,就不累加
wc -l < "$dir_path/$file" 相当于
wc -l "$dir_path/$file" | awk '{print $1}'
补充:比如杀死DataNode节点服务:
kill -9 `jps | grep DataNode | awk '{print $1}'`
一般写法如下:
jps | grep DataNode | awk '{print $1}' | xargs kill -9
xargs kill -9
命令 xargs 是将前面提取到的所有的进程号,作为参数传给命令 kill -9 , 这样我们就能杀死所有的进程。( xargs kill -9 中的 xargs 命令是用来把前面命令的输出结果(PID)作为 kill -9 命令的参数,并执行该令)
[root@bigdata01 scripts]# hadoop-daemon.sh start datanode
WARNING: Use of this script to start HDFS daemons is deprecated.
WARNING: Attempting to execute replacement "hdfs --daemon start" instead.
以上警告的意思是我们的命令可以通过如下方式替换掉。
[root@bigdata01 scripts]# jps | grep DataNode | awk '{print $1}' | xargs kill -9
[root@bigdata01 scripts]# hdfs --daemon start datanode
补充:
1、-a:--all的缩写,显示所有的文件,包括隐藏文件(以.开头的文件)。
2、-A:--almost-all的缩写,显示所有的文件,包括隐藏文件,但不包括表示当前目录.和上级目录..这两个文件
判断条件的含义:
-e filename 如果 filename存在,则为真
-d filename 如果 filename为目录,则为真
-f filename 如果 filename为常规文件,则为真
假设有个变量:file=/dir1/dir2/dir3/my.file.txt
${file#*/}:删掉第一个/ 及其左边的字符串:dir1/dir2/dir3/my.file.txt
${file##*/}:删掉最后一个/ 及其左边的字符串:my.file.txt
${file#*.}:删掉第一个. 及其左边的字符串:file.txt
${file##*.}:删掉最后一个. 及其左边的字符串:txt
${file%/*}:删掉最后一个 / 及其右边的字符串:/dir1/dir2/dir3
${file%%/*}:删掉第一个/ 及其右边的字符串:(空值)
${file%.*}:删掉最后一个 . 及其右边的字符串:/dir1/dir2/dir3/my.file
${file%%.*}:删掉第一个 . 及其右边的字符串:/dir1/dir2/dir3/my
如果您想查找特定目录下包含某个关键字的文件,那么 grep -rl 命令就可以派上用场了。
首先,进入您要搜索的目录。
然后,在终端中输入以下命令:
grep -rl "关键字" 目录名
其中,“关键字”是您要搜索的词语,而“目录名”则是您要查找的目录。
运行该命令后,系统会输出包含“关键字”的所有文件名。
另外,-r 选项表示递归搜索子目录,而 -l 选项表示只输出文件名。
-n : 显示过滤出来的文件在文件当中的行号
wc -l < /etc/services
只返回行数