C语言阶段二·Linuxday4
一、shell中的输入和输出
1.1 输出--echo
类似于 printf
#!/bin/bash
V1=hello
echo $V1 #echo是自带换行符的
echo "hello world"
V2=beijing
echo $V1 $V2 #可以一行输出多个值
#默认是不识别转义字符 \n 的 如果需要识别 得加 -e 选项
echo -e "hello\nworld"
1.2 输入--read
类似于scanf
#!/bin/bash
read V1
echo $V1
echo "---------------------------"
read V1 V2 #多个变量输入时 用空格分隔
#如果要输入的值 本身就有空格
#建议写多行 read 指令 分别获取 否则有冲突
echo $V1
echo $V2
echo "---------------------------"
#如果要输入数组 需要加 -a 选项 输入时要用空格分隔数组元素
read -a ARR
echo ${ARR[*]}
echo ${ARR[0]}
echo ${ARR[1]}
echo "---------------------------"
# -p 可以加描述字段 用作输入的提示
read -p "请输入一个数:" V1
echo $V1
echo "---------------------------"
#read -n 5 表示最多只获取5个字符 输入够5个字符了
#及时不敲回车 也会继续向下执行
read -n 6 V1
echo $V1
echo "---------------------------"
#read -t 5 表示等待输入时只等待 5秒
#超过5秒没输入 就不等了 继续向下执行
read -t 5 V1
echo $V1
echo "---------------------------"
#read -s 表示 取消输入的回显
read -s V1
echo $V1
echo "---------------------------"
#多个选项可以同时使用 但是要注意 选项和参数必须成对存在
read -p "请输入密码:" -n 5 -s PASSWD
echo $PASSWD
练习:
编写脚本:
在终端获取一个要下载的软件名
软件名不能超过20个字符
输入时间不能超过10秒
添加适当的提示信息
隐藏回显的功能
如果输入成功,则下载该软件,并运行该软件
#!/bin/bash
read -n 20 -t 10 -p "请输入要下载的软件名(最多20字符 等待10秒)" -s NAME
sudo apt-get install $NAME #下载
$NAME #执行
二、shell中的运算符
shell本身使用来批量执行命令的,而不是用来做复杂运算的。
如果需要用到复杂的运算,可以使用shell调用C程序来处理问题。
但是有些场景下,简单的运算如果也调用C程序处理的话,就显得有点啰嗦
比如 计数变量的自增。这是,就可以用shell中的运算符来做一些简单的运算了。
shell中如果不使用运算符直接运算,那么默认都会将变量按字符串处理。
shell中的运算符:
(())
$[]
let
expr
上述四种运算符的执行效率从上到下是依次递减的,其中expr可以做字符串处理。
这四种方式至少会用一种(建议记住 (()) 的用法),其他的了解就行。
2.1 (())
((表达式))
((表达式1, 表达式2, ..., 表达式n))
# 当有多个表达式的时候,每个表达式都会参与运算
# 最后一个表达式的结果 就是整个(())运算符表达式的结果
注意事项:
1.在(())内部,引用变量值的时候,$ 可加可不加
2.在(())内部,运算符前后的空格,可加可不加
3.如果想获取表达式的结果 需要在整个(())前加 $ 如 $((A+B))
4.可以做复杂的运算,如 ++ -- for循环
for((i=0; i<100; i++))
例:
#!/bin/bash
read A B
RET1=$((A+B))
RET1=$((A + B))
RET1=$(($A+$B))
RET1=$(($A + $B))
echo $RET1
#只要是用到表达式的结果 都得加 $
echo $((A+B))
echo A=$A
((++A)) #正确的
echo A=$A
#注意 如果是++运算 表达式中变量前就不能加$了
#因为加了之后 相当于 $A=$A+1
#(($A++)) #错误的
#(())支持复杂的运算 如 for循环 ----后面会详细讲
for((i=0;i<10;i++))
echo $i
V3=100
V4=200
RET3=$((++V3, ++V4))
echo $V3 $V4 #1
2.2 $[]
$[表达式]
$[表达式1, 表达式2, ..., 表达式n]
# 当有多个表达式的时候,每个表达式都会参与运算
# 最后一个表达式的结果 就是整个$[]运算符表达式的结果
注意事项:
1.在$[]内部,引用变量值的时候,$ 可加可不加
2.在$[]内部,运算符前后的空格,可加可不加
3.$[]表达式的结果必须有变量来接 或者输出到终端 否则报错
$[A+B] #错误的 RET=$[A+B] echo $[A+B]
4.不可以做复杂的运算
$[i=0; i<100; i++] #错误的写法
例:
#!/bin/bash
V1=10
V2=20
RET1=$[V1+V2]
RET1=$[V1 + V2]
RET1=$[$V1+$V2]
RET1=$[$V1 + $V2]
echo $RET1
RET2=$[++V1, ++V2]
echo $V1 $V2 #11 21
echo $RET2 #21
#表达式的结果必须有变量来接 或者输出到终端
#否则报错
$[V1++] #虽然 V1的值被 ++ 了 但是 会报域法错误
echo $V1 #12
RET3=$[V1++]
echo $V1 #13
echo $[V1++] #13
#$[]不能左复杂的运算
#$[i=0;i<10;i++] #没有这种写法
2.3 expr
1.在expr内部,引用变量值的时候,$ 必须加
2.在expr内部,运算符前后的空格,必须加
3.expr会自动将运算的结果输出到终端
4.如果想获取expr的结果,需要用命令置换符 $() 或者 ``
5.expr不能做复杂的运算 如 ++ --
但是可以用其他的写法实现++的功能,如 V1=`expr $V1 + 1`
6.expr在做乘法运算的时候 * 要写成 \*
#!/bin/bash
v1=10
v2=20
#错误的用法
expr v1+v2 #v1+v2
expr $v1+$v2 #10+20
expr v1 + v2 #非整数参数
#正确的用法
expr $v1 + $v2
RET1=`expr $v1 + $v2`
echo $RET1
RET2=$(expr $v1 + $v2)
echo $RET2
#expr $v1++ #错误的
#可以替换成下面的写法
v1=`expr $v1 + 1`
echo $v1
#RET3=`expr $v1 * $v2` #错误的
RET3=`expr $v1 \* $v2` #正确的
echo $RET3
expr的字符串处理--了解即可,前面学的字符串处理已经够用了
1.expr match
expr match 源字符串 子字符串
在源字符串中查找子字符串
如果源字符串中的,第一个字符和子字符串不相等结果为0
如果子字符串,有字符和源字符串中的字符不相等,返回的也是0.
否则返回的就是子字符串的长度。
#必须是从起点开始匹配 成功才返回长度
string="www.hqyj.com.hqyj.com.qwer"
string2="com"
string3="www.hy"
string4="www.hq"
expr match $string $string2 #0
expr match $string $string3 #0
expr match $string $string4 #6
2.expr substr
string="www.hqyj.com.hqyj.com.qwer"
expr substr $string 5 4 #hqyj
在string中截取从第5个字符开始,向后的4个字符
3.expr index
#提取指定字符的下标,单个字符有意义,如配置文件:
string="my_file=/etc/passwd"
num=`expr index $string "="`
file_path=${string:$num}
echo $file_path
tail -5 $file_path
#多个字符意义不大,如下说明
string="www.hqyj.com.hqyj.com.qwer"
expr index $string "qy" #6
expr index $string "pqy" #6
拿p字符在string查找第一次出现的位置,如果
找到了就返回它在字符串中的下标,然后结束,
否者找不到接着拿q字符在string查找第一次位置
如果找到返回q在字符串中的下标,否则返回0
4.expr length
expr length $string
获取string变量字符串的长度 等价于 ${#string}
2.4 let
let 表达式1 表达式2 ... 表达式n #中间用空格分隔
注意:let必须是完整的算术表达式,即有等号两边
如 let C=A+B 正确
C=`let A+B` 错误
1.let会运算所有的表达式
VAR1=30
VAR2=25
let RET=VAR1+VAR2 RET=RET+VAR1
echo $RET #85
2.let中变量可以加$,也可以不加$
3.运算符的前后不能加空格
三、shell中的 if 语句
3.1 格式
if [ 表达式1 ] #注意:if和[ 之间 [] 和表达式之间 都必须有空格
elif [ 表达式2 ] #注意:elif和[ 之间 [] 和表达式之间 都必须有空格
else #else后面没有 then
fi
3.2 对字符串的判断
类似于strcmp。
注意:对字符串的判断最好加双引号 否则如果参与判断的字符串中有空格 就报错了。
-z 判断字符串是否为空
-n 判断字符串是否为非空
==或者= 判断字符串是否相等
!= 判断字符串是否不相等
\> 判断字符串的大小 大于
\< 判断字符串的大小 小于
拓展:
< 和 << 输入重定向符
> 和 >> 输出重定向符
> 覆盖写
>> 追加写
例:
#!/bin/bash
read -p "input str1:" str1
read -p "input str2:" str2
if [ -z "$str1" ]
echo "空"
if [ -n "$str3" ]
echo "非空"
#判断相等时两个等号和一个等号都可以
#if [ "$str1" = "$str2" ]
if [ "$str1" == "$str2" ]
echo "相等"
echo "不相等"
# != 用法同理
if [ "$str1" \> "$str2" ]
echo "yes"
echo "no"
# \< 用法同理
3.3 将字符串按整型方式判断
-gt 大于 great than
-lt 小于(小写的L) less than
-eq 等于 equal
-ge 大于等于 great equal
-le 小于等于 less equal
-ne 不等于 not equal
逻辑运算
-a 逻辑与 and
-o 逻辑或 or
! 逻辑非
关于逻辑运算符,以逻辑与为例:
在两个 [] 之间要用 &&
在一个 [] 内部要用 -a
例:
#!/bin/bash
read v1
read v2
if [ $v1 -eq $v2 ]
echo "=="
echo "!="
if [ $v1 -gt $v2 ]
echo "v1>v2"
echo "v1<v2"
#if [ $v1 -gt 10 ] -a [ $v1 -lt 20 ] #错误的
#if [ $v1 -gt 10 && $v1 -lt 20 ] #错误的
#if [ $v1 -gt 10 ] && [ $v1 -lt 20 ] #正确的
if [ $v1 -gt 10 -a $v1 -lt 20 ] #正确的
echo "yes"
fi
练习:
输入一个成绩 输出成绩对应的等级 用shell实现一次。
[90, 100] A
[80, 90) B
[0, 80) 不及格
#!/bin/bash
read -p "请输入成绩:" NUM
if [ $NUM -ge 0 -a $NUM -le 100 ]
echo "输入正确"
echo "输入的成绩有误"
if [ $NUM -ge 90 -a $NUM -le 100 ]
echo "A"
elif [ $NUM -ge 80 ] && [ $NUM -lt 90 ]
echo "B"
echo "不及格"
fi
3.4 判断文件的类型
-e 判断文件是否存在 存在为真 不存在为假
-s 判断文件是否存在,并判断文件是否为非空,非空为真 空为假
-b 判断文件是否存在,并且判断文件是否为块设备文件
-S (大写)判断文件是否存在,并且判断文件是否为套接字文件
-p 判断文件是否存在,并且判断文件是否为管道文件
-f 判断文件是否存在,并且判断文件是否为普通文件
-L (大写)判断文件是否存在,并且判断文件是否为软链接文件
-c 判断文件是否存在,并且判断文件是否为字符设备文件
-d 判断文件是否存在,并且判断文件是否为目录文件
注意:
判断类型时,要先判断链接文件,
因为链接文件的类型既算是链接文件 也算是他链接的文件的类型
所以先判断链接文件,是因为其他文件不会被识别成链接文件
例:
输入一个文件,输出文件的类型
#!/bin/bash
read -p "input pathname:" FILENAME
if [ -e $FILENAME ]
echo "存在"
if [ -L $FILENAME ]
echo "链接文件"
elif [ -S $FILENAME ]
echo "套接子文件"
elif [ -p $FILENAME ]
echo "管道文件"
elif [ -b $FILENAME ]
echo "块设备文件"
elif [ -f $FILENAME ]
echo "普通文件"
elif [ -c $FILENAME ]
echo "字符设备文件"
elif [ -d $FILENAME ]
echo "目录文件"
echo "不存在"
fi
3.5 判断文件的权限
-r 判断文件是否存在,并判断是否有读权限
-w 判断文件是否存在,并判断是否有写权限
-x 判断文件是否存在,并判断是否有执行权限
注意: 哪个用户来执行脚本,判断的就是改基本对哪个用户的权限
#!/bin/bash
read -p "input pathname:" FILENAME
if [ -w $FILENAME ] #读权限和执行权限判断方式同理
echo "yes"
echo "no"
fi
3.6 判断文件的时间戳
时间戳:文件最后一次修改的时间
-nt 判断前面文件的时间戳是否比后面的新 new than
-ot 判断前面文件的时间戳是否比后面的旧 old than
注意:离当前时间越近,时间戳越新。
#!/bin/bash
read -p "input file1:" NAME1
read -p "input file2:" NAME2
if [ $NAME1 -nt $NAME2 ]
echo "$NAME1 新"
echo "$NAME2 新"
fi
练习:
输入一个文件
判断该文件是否存在,
如果存在,判断是否是普通文件
如果是,判断是否有写权限
如果有,将 "hello world" 追加到文件最后(echo "hello world" >> file)
哪个条件不符合,则输出对应的错误。
#!/bin/bash
read -p "input pathname:" NAME
if [ -e $NAME ]
if [ -f $NAME ]
if [ -w $NAME ]
echo "hello world" >> $NAME
echo "没有写权限"
echo "不是普通文件"
echo "不存在"
fi
四、case..in 语句
4.1 格式
case $变量 in
esac
注意:
case 后面一定是变量的值 也就是说变量名前一定有 $
选项中的值就是case后面的变量所有可能的结果
* 选项表示其他分支,相当于switch..case中的 default分支
4.2 关于case的选项
选项是支持通配符的
[0-9] 通配0-9中的任意一个字符
[a-zA-Z] 通配 a-zA-Z中的任意一个字符 注意 涉及本地语序 LC_ALL=C
"aa"|"bb"|"cc" 通过 aa 或者 bb 或者 cc
[abc] 通配[abc]中的任意一个字符
[^abc] 通配除了[abc]中的任意一个字符。。。这种写法逻辑是反的 基本不使用
例:
#!/bin/bash
read -p "input a value:" VALUE
case $VALUE in
"beijing"|"shanghai")
echo "房价贵"
class[0-9])
echo "教室名"
[a-z])
echo "a-z"
hqyj[abc])
echo "hqyja hqyjb hqyjc"
echo "other"
esac
思考:
编写脚本的部分功能:
获取用户输入的内容,并决定是否下载某个软件
要求:不管用户输入的是 Y y YES yes Yes yEs YEs yeS ... 都识别为 下载
#!/bin/bash
read -p "您真的要下载该软件嘛 [Y/n]" CHOOSE
case $CHOOSE in
[Yy][Ee][Ss]|[Yy])
echo "yes"
echo "no"
esac
五、shell中的while循环
5.1 格式
while 表达式
done
其中表达式的用法和 if 的表达式一样
例:1~10 求和
#!/bin/bash
VALUE=1
SUM=0
while [ $VALUE -le 10 ]
SUM=$((SUM+VALUE))
((VALUE++))
echo $SUM #55
5.2 死循环
while [ 1 ]
while true
while ((1))
done
例如:使用shell中死循环每隔1秒输出系统时间
#!/bin/bash
while [ 1 ]
sleep 1
while ((1))
sleep 1
while true
sleep 1
done
练习:
使用while循环实现一个累加器的功能
可以一直在终端输入数据,直到遇到 "#" 结束
结束后,输出所有数据求和的结果。
#!/bin/bash
read VALUE
SUM=0
while [ $VALUE != "#" ]
SUM=$[$SUM+$VALUE]
read VALUE
echo $SUM
六、shell中的for循环
6.1 C风格的for循环
for ((i=0;i<10;i++))
done
6.2 shell中特有的for循环
格式:
for 变量 in 单词列表
done
关于单词列表:
1.单词列表中的单词用空格分隔
2.如果单词列表是连续的,可以使用 {start..end} 如 {1..100}
也可以使用序列来处理 `seq 1 100` #遍历 [1, 100]
也可以按照一定的间隔取数据 `seq 1 2 100` #从1开始取数据 步长为2 到100结束
3.单词列表也可以是命令的结果(只要命令的结果是以空格分隔的就可以)
4.省略 in 的 for循环,这时 变量的值来自于执行脚本时 命令行的参数
例:
#!/bin/bash
for i in aa bb cc dd
echo $i
echo "-------------------"
for i in {1..10}
echo "hello $i"
echo "-------------------"
for i in `seq 1 20`
echo "hqyj $i"
echo "-------------------"
for i in `seq 1 2 20`
echo "hqyj $i"
echo "-------------------"
for i in `ls` #常用的写法 可以操作多个文件
chmod a+x $i
echo "-------------------"
#i 的值来自 命令行的参数
for i
echo $i
done
练习:
编写脚本,实现文件分类的功能。
注意:在脚本中使用家目录时 最好不要使用 ~ 最好使用环境变量 $HOME
1.判断一下当前用户的家目录下是否存在 dir_dir 和 file_dir 两个目录文件
如果存在,则询问用户是否清空
如果用户输入的是:Y 则清空(删除 并 新建)
如果用户输入的是其他字符串,则不清空直接退出程序
如果不存在则直接新建。
2.在终端输入一个 绝对路径
将该目录下的所有普通文件都复制到 file_dir
将该目录下的所有目录文件都复制到 dir_dir
3.将file_dir中所有的 .c 文件 g-w
4.输出复制的 普通文件 和 目录文件 的个数
5.将 file_dir 归档并压缩 file_dir.tar.gz
6.将 dir_dir 归档并压缩 dir_dir.tar.gz
7.列出家目录下所有文件的信息
8.列出dir_dir目录下所有文件的信息
9.列出file_dir目录下所有文件的信息
#!/bin/bash
MY_DIR=$HOME/dir_dir
MY_FILE=$HOME/file_dir
FILE_COUNT=0
DIR_COUNT=0
if [ -e $MY_DIR ]
read -p "$MY_DIR 已存在,是否清空[Y/n]" CHOOSE
if [ $CHOOSE == "Y" ]
rm -rf $MY_DIR
mkdir $MY_DIR
echo "不清空,程序已退出"
mkdir $MY_DIR
if [ -e $MY_FILE ]
read -p "$MY_FILE 已存在,是否清空[Y/n]" CHOOSE
if [ $CHOOSE == "Y" ]
rm -rf $MY_FILE
mkdir $MY_FILE
echo "不清空,程序已退出"
mkdir $MY_FILE
#输入要操作的路径
read -p "请输入要操作的目录文件的 绝对路径:" PATHNAME
if [ -e $PATHNAME ]
if [ -d $PATHNAME ]
for i in `ls $PATHNAME`
if [ -d $PATHNAME/$i ]
cp -r $PATHNAME/$i $MY_DIR
((DIR_COUNT++))
elif [ -f $PATHNAME/$i ]
cp $PATHNAME/$i $MY_FILE
((FILE_COUNT++))
echo "要操作的目录不存在 程序退出"
cd $MY_FILE
find ./ -type f |grep "\.c"|xargs chmod g-w
echo "共复制了 $DIR_COUNT 个目录文件"
echo "共复制了 $FILE_COUNT 个普通文件"
cd $HOME
tar -zcf dir_dir.tar.gz dir_dir
tar -zcf file_file.tar.gz file_dir
echo "------------HOME LIST--------------"
ls -l $HOME