字符串与基本运算
1 变量赋值=
与变量取值 $
变量名直接后接等于号=
表示赋值。注意等于号两边不能有空格。
直接用$
跟变量名,表示取值。
# 简单字符串可以raw表示,无需加引号
a=hello
echo $a
# 带空格的字符串需要以引号包裹
a=hello world # 报错 world: command not found
b="hello world"
echo $a
echo $b
2 命令运算$()
()
表示运行括号里的命令,$
表示拦截这个命令的输出给左边的变量。
命令运算等效于在terminal里直接输入command然后捕获标准输出流。
# 打印当前目录
a=$(pwd)
echo $a # /root
3 整数运算$(( ))
整数运算满足in和out都是整数。
- 操作数必须是整数。
- 结果会自动取整(floor)。
a=1
b=2
c=0.7
res1=$(( a+b ))
res2=$(( a/b ))
echo $res1 # 30
echo $res2 # 0 = floor(0.5)
# 操作数不是整数,报错
# syntax error: invalid arithmetic operator (error token is ".7")
res3=$(( b*c ))
# 还支持其他运算
res4=$(( a|b ))
res5=$(( a&b ))
res6=$(( a^b ))
res7=$(( a<<b ))
res8=$(( b>>1 ))
4 变量替换${}
和命令运算$()
不同,变量替换${}
不执行command,仅仅将此处替换为某个变量的字符串值。
和变量取值$
不同,变量替换${}
除了取值功能,还能定义默认值,以及访问数组。
name="apple"
aaa="name is: ${name}"
#在双引号环境下,等价于
aaa="name is: $name"
# 但替换操作符支持默认值
myname=""
aaa="name is: ${myname:-'dft'}"
echo $aaa
# 还支持数组顺序访问
array=(1 2 3)
aaa="name is: ${array[0]}"
echo $aaa # name is: 1
# 感受一下为什么不用变量取值`$`
bbb="name is: $array[0]"
echo $bbb # name is: 1[0]
位置参数
举例:
sh demo.sh 123 456
$0 表示 demo.sh
$1 表示123
$0 : 在用sh 或者 ./执行脚本时,指的是脚本名,用source或.执行时,永运是bash,这也反应了sh 或者 ./执行脚本的原理和source的方式是不同的.
$1 : 第一个参数 123
$2 : 第二个参数 456
依次类推
$# : 参数的个数,不包括命令本身,上例中为2
$@ : 参数本身的列表,也不包括命令本身,如上例为[123,456]
$* : 参数本身的列表,也不包括命令本身, "$*“将所有的参数解释成一个字符串,而”$@"是一个参数数组。
注意,函数内的参数是独立的,不与整个脚本的参数关联。
命名参数
命名参数的传递通常使用getopt/getopts。
getopts只能处理短参数格式,兼容linux和mac。
getopt能处理长参数和短参数格式,mac上默认不支持。
因为mac下的getopt由BSD实现,Linux则是GNU,导致一些不通用,建议在mac上安装GNU-getopt。
getopts 是 Shell 内建命令,getopt 是一个独立外部工具。
为了保持跨系统兼容,我先使用getopts,仅支持单字母。
语法
单字母,bool参数,默认false。
单字母加冒号:
,值参数。
如果命令行中包含了没有在getopts列表中的选项,会有警告信息,如果在整个getopts字符串前面也加上个:,就能消除警告信息了。
使用getopts识别出各个选项之后,操作中有两个相对固定的“常量”,一个是OPTARG,用来取当前选项的值,另外一个是OPTIND,
getopts在处理参数的时候,处理一个开关型选项,OPTIND加1,处理一个带值的选项参数,OPTIND则会加2。
#!/bin/bash
echo 初始 OPTIND: $OPTIND
while getopts "a:b:c" arg #选项后面的冒号表示该选项需要参数
do
case $arg in
a)
echo "a's arg:$OPTARG" #参数存在$OPTARG中
;;
b)
echo "b's arg:$OPTARG"
;;
c)
echo "c's arg:$OPTARG"
;;
?) #当有不认识的选项的时候arg为?
echo "unkonw argument"
exit 1
;;
esac
done
echo 处理完参数后的 OPTIND:$OPTIND
echo 移除已处理参数个数:$((OPTIND-1))
shift $((OPTIND-1))
echo 参数索引位置:$OPTIND
echo 准备处理余下的参数:
echo "Other Params: $@"
字符串运算
根据变量值,动态计算字符串。
直接相邻时,有没有引号是一样的。
$script_dir/repos.txt
"$script_dir/repos.txt"
不直接相邻时,带引号才能完成格式化拼接。
in $script_dir there is repos.txt
"in $script_dir there is repos.txt"
如果直接跟着合法变量名字符,需要加括号
"$script_diris dir"
"${script_dir}is dir"
如果跟的不是合法变量名字符,不需要加,或者说加不加等价。
"$script_dir/aa.txt"
"${script_dir}/aa.txt"
条件判断
if [ expression ]
then
Statement(s) to be executed if expression is true
else
Statement(s) to be executed if expression is not true
fi
结合上面的知识,可以得到几个应用
参数判空
这个需求挺常见的吧。比如find搜索目标文件。检查搜索结果是否为空。
变量判空
if [ ! -n "$arg_dir" ] ; then
repos_dir=$script_dir/repos
else
repos_dir=$(readlink -f $arg_dir)
fi
注意 "$var_name"外面的双引号很重要。不加就不能正确判空。
我还不知道为什么这里必须加双引号。
数组判空
if [ -z "${array[@]}" ]; then
echo "数组为空"
else
echo "数组不为空"
fi
判断变量存在
test $this_val
判断文件/目录存在
-d判断目录
-f判断文件
#如果文件夹不存在,创建文件夹
if [ ! -d "myfolder" ]; then
mkdir myfolder
fi
比较两个字符串是否相等
if [ "$test"x = "test"x ]; then
dosomethinghere
fi
这里的关键有几点:
1 使用单个等号
2 注意到等号两边各有一个空格:这是unix shell的要求
3 注意到"$test"x
最后的x,这是防止出错特意安排的。因为当$test
为空的时候,上面的表达式就变成了x = testx
,显然是不相等的。而如果没有这个x,表达式就会报错:[: =: unary operator expected
。
循环读取行
function clone(){
while read line || [[ -n ${line} ]]
do
echo "read $line"
done < $1
}
clone 1.txt
注意 ||
后面的条件判断,是为了防止while读最后一行内容时遇到EOF直接退出,导致最后一行内容不能进入do循环体的问题。
参照//https://www.cnblogs.com/Braveliu/p/10573389.html
或者用for
IFS=$'\n' # 修正换行符
function clone(){
for line in `cat $1`
do
echo "read $line"
done
}
clone 1.txt
路径计算
当前脚本的路径
script_path=$(readlink -f $0)
script_dir=$(dirname $(readlink -f $0))
修改文件内容
sed
指令。
指定模式串匹配
两个冒号之间是要查找的内容
第二个冒号对之间是替换的内容
sed -i -e “s:AllowUsers.*:AllowUsers root:g” /etc/ssh/sshd_config
sed 后的指令最好单引号包裹。
sed的指令模式中 直接用数字表示行号。
例如删除第四行, sed -e '4d' test.txt
。
行返回用逗号分割,例如 sed -e '2,4d' test.txt
表示删除2~4行。
$
表示文档结尾。 sed -e '2,$d' test.txt
表示从2删到结尾。
插入
sed '2i drink tea'
, 第二行后 插入 drink tea
。
sed '2a Drink tea or \ drink beer'
反斜杠标记新行。
s
指令代表替换,是最常用的。 s通常和正则表达式一起出现。
sed -e 's:{匹配表达式}:{想要替换的内容}'
shell字符串处理练习
Q:请输出 ====start{换行} hello {换行}end===
猜测下面哪个写法是对的?
echo ===\n hello \n===
echo "===\n hello \n==="
echo ===\\n hello \\n===
echo "===\\n hello \\n==="
打印结果 都不对
===n hello n===
===\n hello \n===
===\n hello \n===
===\n hello \n===
需要使用echo -e
开启转义
echo -e ===\n hello \n===
echo -e "===\n hello \n==="
echo -e ===\\n hello \\n===
echo -e "===\\n hello \\n==="
===n hello n===
# 第二个
===
hello
===
# 第三个
===
hello
===
# 第四个
===
hello
===
输出文本到文件
常见需求,输出命令
# 先make -p ${parent_dir}确认路径是存在的
# 输出单行,内容覆盖
echo "export PATH=/root/my/:$PATH" > /root/test/myscript.sh
# 输出单行,内容追加到文件尾
echo "export PATH=/root/my/:$PATH" >> /root/test/myscript.sh
# 输出多条命令,可以用分号隔开
echo "export PATH=/root/my/:$PATH; echo 456;" > /root/test/myscript.sh
# 输出多条命令,可以用换行符
# 注意转义字符需要-e开启
echo -e "export PATH=/root/my/:$PATH\n echo 456" > /root/test/myscript.sh
额外介绍一下多行文本重定向。
语法: <<{标识符}
标识符EOF
之间的内容将被作为多行文本重定向给前面的命令
# 输出多条命令,使用多行文本重定向
cat /root/test/myscript.sh<<EOF
this is a file created by shell.
we want to make a good world.
EOF
标识符的名字可以任意更改,通常约定使用全大写。标识符前后不能有空格。
# 使用多行文本重定向,修改标识符名字
cat /root/test/myscript.sh<<END
this is a file created by shell.
we want to make a good world.
END
cat /root/test/myscript.sh<<HELLO
this is a file created by shell.
we want to make a good world.
HELLO
可以接受多行文本重定向的指令还有一些
cat /root/test/myscript.sh<<EOF
this is a file created by shell.
we want to make a good world.
EOF
grep /root/test/myscript.sh<<EOF
this is a file created by shell.
we want to make a good world.
EOF
python <<EOF
this is a file created by shell.
we want to make a good world.
EOF