SHELL编程基础
shell 是一种脚本语言(解释型语言),需要解释器来执行。
Linux上的常见shell解释器有bash,zsh等
以下环境为bash
第一个shell脚本
#!/bin/bash “#!” : 告诉系统这个脚本需要什么解释器
echo 'Hello World!'
运行方式:
1. bash first.sh
2. chmod +x first.sh
./first.sh
变量与局部变量
定义变量与使用变量
- 定义变量时,变量名不加美元符号($),如 : ABC=“123”
- 变量名的命名须遵循如下规则:
- 首个字符必须为字母(a-z,A-Z)
- 中间不能有空格,可以使用下划线(_)
- 不能使用标点符号
- 不能使用 bash 里的关键字
- 使用变量
- 使用一个定义过的变量,只要在变量名前面加美元符号($)即可
A ="123"
echo $A
echo ${A}
变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界。
变量类型
运行 shell 时,会同时存在三种变量 :
1) 局部变量
局部变量在脚本或命令中定义,仅在当前 shell 实例中有效,其他 shell 启动的程序不能访
问局部变量。
2) 环境变量
所有的程序,包括 shell 启动的程序,都能访问环境变量,有些程序需要环境变量来保证其
正常运行。必要的时候 shell 脚本也可以定义环境变量。
3) shell 变量
shell 变量是由 shell 程序设置的特殊变量。shell 变量中有一部分是环境变量,有一部分
是局部变量,这些变量保证了 shell 的正常运行。
Shell特殊变量
前面已经讲到,变量名只能包含数字、字母和下划线,因为某些包含其他字符的变量有特殊
含义,这样的变量被称为特殊变量。
特殊变量 | 含义 |
---|---|
$0 | 当前脚本文件名 |
$n | 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2。 |
$# | 传递给脚本或函数的参数个数。 |
$* | 传递给脚本或函数的所有参数。 |
$@ | 传递给脚本或函数的所有参数。 |
$? | 判断上条指令是否成功,0为成功, 非零为不成功 |
$$ | 当前 Shell 进程 ID。对于 Shell 脚本,就是这些脚本所在的进程 ID。 |
$! | 上一个指令的PID |
变量,参数展开
${parameter:-word} 如果变量未定义,则表达式的值为word
用于对脚本的可靠性判断:
Dir=/home/xxxxx/mnt2
find ${Dir} -name "*.gz" -type f | xargs rm -rf
如果某一时刻,Dir目录为空时,
rm -rf ${Dir}/* 这时就出大问题
所以先判断一下变量有没有被定义,相当于在未定义时,
将${Dir}这个表达式的返回值设为默认值,此时Dir仍为空。
${parameter:=word} 如果变量未定义,则设置变量的值为word,返回表达式的值也是word
${parameter:?word} 检测变量是否存在,不存在返回word,用于捕捉由于变量未定义而导致的错误并退出程序
find ${Dir:?"Not Found"} -name "*.gz" -type f | xargs rm -rf
写成这样就比较安全
${parameter:+word} 如果变量已经定义,返回word
${!prefix*} ${!prefic@} prefix开头的所有变量的名字
字符串展开
${#parameter} 输出字符串长度
${parameter:offset} 从第offset字符开始截取,从0开始计数
${parameter:offset:length} 从第offset字符开始截取,取length长度
以下用的很少
${parameter#pattern} 从头删除最短匹配
${parameter##pattern} 从头删除最长匹配
${parameter%pattern} 从尾删除最短匹配
${parameter%%pattern} 从尾删除最长匹配
字符串匹配与替换
${parameter/pattern/string} 第一个匹配被替换
${parameter//pattern/string} 第一个匹配被替换
${parameter#pattern/string} 字符串开头的替换
${parameter%pattern/string} 字符串结尾的替换
${parameter,,} ${parameter^^}全部替换成小写,大写
${parameter,} ${parameter^} 首字母替换成小写,大写
输入输出
echo
printf
printf "%s is %d years old\n" "bird" 18 #与c语言相似
read
用到的比较多的是 -t :限制输入的时间 -p:输入显示提示 -s:静默模式,输密码时,不显示出来
read -p "Please input your rassword:"
read -s -p "Please input your rassword:"
read -s -t 1 -p "Please input your rassword:"
函数
三种定义方式,记住第三种
fuction _printf_ {
echo $1
return
}
_printf_() {
echo $1
return
}
function _printf_() {
echo $1
return
}
调用:
_printf_ "Hello World!"
流程控制
IF
age=$1
#test表达式 -eq -ne -gt -lt -ge -le
if [[ $age -gt 18 ]]; then
echo "> 18"
else
echo "< 18"
fi
if [[ $age -gt 18 ]]; then
echo "> 18"
elif [[ $age -le 18 ]]; then
echo "< 18"
fi
WHILE
#!/bin/bash
#从100打印到0
num=100
while [[ ${num} -ge 0 ]];do
echo &{num}
num=$[${num} - 1]
done
FOR
for (( i = 0; i <= 100; i++ ));do
echo ${i}
done
for i in `seq 0 100`;do
echo $i
done
UNTIL
直到until后面的条件成立时,就结束
#!/bin/bash
#输出0~100
sum=0
until [[ ${sum} -gt 100 ]];do
echo ${sum}
sum=$[${sum}+1]
done
CASE
read age
case $age in
1 )
echo "1"
;;
2 )
echo "2"
;;
esac
数组与数组操作
bash 支持一维数组(不支持多维数组),并且没有限定数组的大小。
类似与 C 语言,数组元素的下标由 0 开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于 0。
定义数组
array_name=(value0 value1 value2 value3)
array_name=(
value0
value1
value2
value3
)
还可以单独定义数组的各个分量:
array_name[0]=value0
array_name[1]=value1
array_name[2]=value2
可以不使用连续的下标,而且下标的范围没有限制。
读取数组
valuen=${array_name[2]}
#使用@ 或 * 可以获取数组中的所有元素,例如:
${array_name[*]}
${array_name[@]}
#找到数组下标
${!array[@]} #数组中各项对应的下标
# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthn=${#array_name[n]}
数组追加
array+=(a b c)
#例子
name[0]=a
name[1]=b
name+=(1 2 3)
echo ${name[*]}
#输出 a b 1 2 3
数组排序
用管道 sort 排一下就可以
删除数组与元素
unset name[100] #清空name[100]这个位置
实践 1
求一定范围内的素数和
采用线性筛
传入两个参数,起始数字,终止数字
如果起始数字<0, 则从0开始
使用方式 : bash Prime.sh 0 1000
#!/bin/bash
declare -a prime #这里定义了一个数组prime,不定义直接用也没有关系
sum=0
function usage() {
printf "Usage: %s start_num end_num\n" $0
}
function init() {
S=$1
E=$2
for (( i=$S; i<=$E; i++ ));do
prime[$i]=0
done
}
if [[ $# -ne 2 ]];then
usage
exit
fi
start_num=$1
end_num=$2
if [[ $start_num -lt 0 ]];then
start_num=0
fi
if [[ ${start_num} -gt ${end_num} ]];then
Usage
exit
fi
init 0 ${end_num}
for (( i=2; i<=${end_num}; i++));do
if [[ ${prime[$i]} -eq 0 ]];then
prime[0]=$[ ${prime[0]} + 1 ]
prime[${prime[0]}]=$i
if [[ $i -ge ${start_num} ]];then
sum=$[ ${sum} + $i ]
fi
fi
for (( j=1; j<=${prime[0]}; j++ ));do
if [[ ${i} * ${prime[$j]} -gt ${end_num} ]];then
break
fi
prime[$[ ${i} * ${prime[$j]} ]]=1
if [[ $[ $i % ${prime[$j]} ] -eq 0 ]];then
break
fi
done
done
echo $sum
实践 2
求一个文件中的最长字符串 , 用命令实现,非脚本
先要拿到文件中的所有字符串
使用tr指令
tr [-cdst][--help][--version][第一字符集][第二字符集]
-c, --complement:反选设定字符。也就是符合 SET1 的部份不做处理,不符合的剩余部份才进行转换
-s, --squeeze-repeats:缩减连续重复的字符成指定的单个字符
cat a.cpp | tr -s -c "a-zA-Z" " "
-c: 反选,不是“a-zA-Z”的一律改为“ ”
-s: 压缩 不是“a-zA-Z”的多个字符,一律压缩成一个字符
#!/bin/bash
max_len=0
max_string=''
if [[ $# -lt 1 ]];then
printf "Usage: %s file[...]\n" $0
exit
fi
for i in `cat $1 | tr -s -c "a-zA-Z" "\n"`;do
len=${#i}
if [[ ${len} -gt ${max_len} ]];then
max_len=$len
mex_string=$i
fi
done
echo $max_string $max_len
得到结果
function 8
#之后想要的到所在行号:
grep -n "function" a #在a文件中找到"function"
实践 3
递归查找文件中的最长字符串
小插曲:如何读取stdio.h文件的行数
wc -l `locate stdio.h | head -n 1`
locate stdio.h 拿到许多路径
head -n 1 拿到许多路径中的第一个
wc -l 统计文件中的行数
正题:
分成两个文件共同完成这个功能
- func.sh
#!/bin/bash
function find_max_in_file() {
echo "finding max_string in file $1"
words=`cat $1 | tr -s -c "a-zA-Z" "\n"`
for i in ${words};do
len_t=`echo -n ${i} | wc -c`
if [[ ${len_t} -gt ${len_max} ]];then
len_max=${len_t}
max_string=${i}
max_file=$1
fi
done
}
function find_max_in_dir() {
for i in `ls -A $1`;do
if [[ -d ${1}/${i} ]];then
echo "${1}/$i is a dir"
find_max_in_dir ${1}/${i}
else
echo "${1}/$i is a file"
find_max_in_file ${1}/${i}
fi
done
}
注1:这里注意在dir中遍历其子文件时,要把"." 和"…"去掉,不然会无休止的递归
去掉的方法, ls -A 不同于ls -a, 没有".“和”…"
注2:以上还有一个问题,需要对文件种类进行过滤,两种过滤方法:
一个是用test表达式去判断是不是普通文件
一个是用文件名后缀来过滤
方法一:在原第4行和第5行之间添加test表达式判断
#!/bin/bash
function find_max_in_file() {
echo "finding max_string in file $1"
# 用test表达式来判断文件是不是regular文件
if [[ ! -f ${1} ]];then
echo "$1 is not a r_file!\n"
return
fi
words=`cat $1 | tr -s -c "a-zA-Z" "\n"`
for i in ${words};do
len_t=`echo -n ${i} | wc -c`
if [[ ${len_t} -gt ${len_max} ]];then
len_max=${len_t}
max_string=${i}
max_file=$1
fi
done
}
function find_max_in_dir() {
for i in `ls -A $1`;do
if [[ -d ${1}/${i} ]];then
echo "${1}/$i is a dir"
find_max_in_dir ${1}/${i}
else
echo "${1}/$i is a file"
find_max_in_file ${1}/${i}
fi
doneif [[ ! -f ${1} ]]
}
方法二: 添加一个黑名单
#!/bin/bash
filter_types=(mp4 avi gz zip tar)
function filter(){
file_name=$1
type_name=`basename ${file_name} | rev | cut -d "." -f 1 | rev`
for i in ${filter_types[@]};do
if [[ ${file_name} == $i ]];then
echo "Filter On! ${file_name}"
return 1 #这里返回1
fi
done
}
function find_max_in_file() {
if [[ ! -f ${1} ]];then
echo "$1 is not a r_file!\n"
return
fi
#这里用bilter函数判断一下是否为黑名单中的后缀文件
filter $1
if [[ $? -eq 1 ]];then #查看filter函数是否正常退出, 如果返回值是1,则直接返回
return
fi
echo "finding max_string in file $1"
words=`cat $1 | tr -s -c "a-zA-Z" "\n"`
for i in ${words};do
len_t=`echo -n ${i} | wc -c`
if [[ ${len_t} -gt ${len_max} ]];then
len_max=${len_t}
max_string=${i}
max_file=$1
fi
done
}
function find_max_in_dir() {
for i in `ls -A $1`;do
if [[ -d ${1}/${i} ]];then
echo "${1}/$i is a dir"
find_max_in_dir ${1}/${i}
else
echo "${1}/$i is a file"
find_max_in_file ${1}/${i}
fi
done
}
- find.sh
#!/bin/bash
len_max=0
max_string=""
max_file=""
source find_max_in_dir.sh
if [[ $# -eq 0 ]];then
find_max_in_dir "."
else
for i in $@;do
if [[ -d $i ]];then
find_max_in_dir $i
else
find_max_in_file $i
fi
done
fi
#彩色打印结果
printf "The max string is \033[31;34m%s \033[0m , with length \033[31;34m %d \033[0m in file \033[31;34m %s\033[0m\n" $max_string $len_max $max_file