Shell_基础学习

项目

  • 工作中涉及到了一个shell脚本,其中要完成进程的启动、向服务端打点、接收服务端返回内容进行4中操作,大致就是这么多功能。由于版本迭代的很快,这个脚本一直没人维护,索性我来弄吧。
  • 之前没接触过shell,这次算是第一次正式的写一个应用级的shell脚本,在写的过程中,顺便将涉及到的shell基础知识做了个学习笔记。

学习笔记

基本命令

命令 用法含义
$$ 获取当前进程PID
$# 返回参数的数量
$! 最后运行的后台进程的PID
1-n 返回第一个参数、到第n个参数值
$@ 返回全部参数
$? 返回上一个命令执行的结果,0:执行成功;1:执行失败
| 单竖线,将‘|’前面命令的输出作为'|'后面的输入
|| 双竖线分割的多条命令,执行的时候遵循如下规则,如果前一条命令为真,则后面的命令不会执行,如果前一条命令为假,则继续执行后面的命令。
& &同时执行多条命令,不管命令是否执行成功
&& && 可同时执行多条命令,当碰到执行错误的命令时,将不再执行后面的命令。如果一直没有错误的,则执行完毕。
exit 退出当前进程,命令可以接受一个整数值作为参数,代表退出状态。如果不指定,默认状态值是 0。0 表示成功,非0表示失败 例如:echo "befor exit" exit 8 echo "after exit" echo "$?" 结果为 befor exit 8
-z if表达式[ -z $string ] 如果string 为空则为真
-n if表达式[ -n $string ] 如果string 非空(非0),返回0(true)
-s if表达式[ -s FILE ] 如果 FILE 存在且大小不为0则为真。
-a if表达式[ -a FILE ] 如果 FILE 存在则为真。

if语句

  • 语法
if [ command ];then
     符合该条件执行的语句
elif [ command ];then
     符合该条件执行的语句
else
     符合该条件执行的语句
fi
  • 说明
    • 1、[ ]表示条件测试。注意这里的空格很重要。要注意在'['后面和']'前面都必须要有空格
    • 2、在shell中,then和fi是分开的语句。如果要在同一行里面输入,则需要用分号将他们隔开。
    • 3、注意if判断中对于变量的处理,需要加引号,以免一些不必要的错误。没有加双引号会在一些含空格等的字符串变量判断的时候产生错误。比如[ -n "$var" ]如果var为空会出错
    • 4、判断是不支持浮点值的
    • 5、如果只单独使用>或者<号,系统会认为是输出或者输入重定向,虽然结果显示正确,但是其实是错误的,因此要对这些符号进行转意
    • 6、在默认中,运行if语句中的命令所产生的错误信息仍然出现在脚本的输出结果中
    • 7、使用-z或者-n来检查长度的时候,没有定义的变量也为0
    • 8、空变量和没有初始化的变量可能会对shell脚本测试产生灾难性的影响,因此在不确定变量的内容的时候,在测试号前使用-n或者-z测试一下
    • 9、? 变量包含了之前执行命令的退出状态(最近完成的前台进程)(可以用于检测退出状态)

数值判断

[ INT1 -eq INT2 ] INT1和INT2两数相等返回为真 ,=
[ INT1 -ne INT2 ] INT1和INT2两数不等返回为真 ,<>
[ INT1 -gt INT2 ] INT1大于INT2返回为真 ,>
[ INT1 -ge INT2 ] INT1大于等于INT2返回为真,>=
[ INT1 -lt INT2 ] INT1小于INT2返回为真 ,<
[ INT1 -le INT2 ] INT1小于等于INT2返回为真,<=

#*,##*,% *,%% *的含义及用法

假设定义了一个变量为:
代码如下:
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
记忆的方法为:
# 是 去掉左边(键盘上#在 $ 的左边)
% 是去掉右边(键盘上% 在$ 的右边)
单一符号是最小匹配;两个符号是最大匹配
${file:0:5}:提取最左边的 5 个字节:/dir1
${file:5:5}:提取第 5 个字节右边的连续5个字节:/dir2
也可以对变量值里的字符串作替换:
${file/dir/path}:将第一个dir 替换为path:/path1/dir2/dir3/my.file.txt
${file//dir/path}:将全部dir 替换为 path:/path1/path2/path3/my.file.txt

符号$后的括号

  • ${a} 变量a的值, 在不引起歧义的情况下可以省略大括号.
  • $(cmd) 命令替换, 结果为shell命令cmd的输出, 和cmd效果相同, 不过某些Shell版本不支持$()形式的命令替换, 如tcsh.
  • $((exp)) 和expr exp效果相同, 计算数学表达式exp的数值, 其中exp只要符合C语言的运算规则即可, 甚至三目运算符和逻辑表达式都可以计算.

标准输入、标准输出、标准错误

/dev/null 代表空设备文件
> 代表重定向到哪里,例如:echo "123" > /home/123.txt
1 表示stdout标准输出,系统默认值是1,所以">/dev/null"等同于"1>/dev/null"
2 表示stderr标准错误
& 表示等同于的意思,2>&1,表示2的输出重定向等同于1
重定向的使用有如下规律:
1)标准输入0、输出1、错误2需要分别重定向,一个重定向只能改变它们中的一个。
2)标准输入0和标准输出1可以省略。(当其出现重定向符号左侧时)
3)文件描述符在重定向符号左侧时直接写即可,在右侧时前面加&。
4)文件描述符与重定向符号之间不能有空格!
eg:
command <   filename  >   filename2     把标准输入重定向到filename文件中,把标准输出重定向到filename2文件中
command 0<  filename 1>   filename2     把标准输入重定向到filename文件中,把标准输出重定向到filename2文件中
command >   filename 2>&1               把标准输出和标准错误一起重定向到filename文件中(覆盖)
command >>  filename 2>&1               把标准输出和标准错误一起重定向到filename文件中(追加)
command <   filename                    把标准输入重定向到filename文件中
command 0<  filename                    把标准输入重定向到filename文件中
command >   filename                    把标准输出重定向到filename文件中(覆盖)
command 1>>  fielname                   把标准输出重定向到filename文件中(追加)

软连接

    ln –s 源文件 目标文件

等号与双等号的区别

  • == 可用于判断变量是否相等,= 除了可用于判断变量是否相等外,还可以表示赋值。
  • = 与 == 在 [ ] 中表示判断(字符串比较)时是等价的,例如:
s1="foo"
s2="foo"
[ $1=$2 ] && echo "equal"
[ $1==$2 ] && echo "equal"
  • 在 (( )) 中 = 表示赋值, == 表示判断(整数比较),它们不等价,比如:
((a=5))
echo $a  # 此时为赋值操作
((a==5)) && echo "equal"    # 此时表示判断

单引号和双引号的区别

#!/bin/bash
url="http://c.biancheng.net"
website1='C语言中文网:${url}'
website2="C语言中文网:${url}"
echo $website1
echo $website2
运行结果:
C语言中文网:${url}
C语言中文网:http://c.biancheng.net
  • 以单引号' '包围变量的值时,单引号里面是什么就输出什么,即使内容中有变量和命令(命令需要反引起来)也会把它们原样输出。这种方式比较适合定义显示纯字符串的情况,即不希望解析变量、命令等的场景。
  • 以双引号" "包围变量的值时,输出时会先解析里面的变量和命令,而不是把双引号中的变量名和命令原样输出。这种方式比较适合字符串中附带有变量和命令并且想将其解析后再输出的变量定义。

小括号与大括号区别

小括号()

①命令组。括号中的命令新开一个子shell程序,括号中的变量为本地变量 ,不能够在脚本其他部分使用。括号中多个命令之间用分号隔开。
备注:
在括号中的变量,由于是在子shell中,所以对于脚本剩下的部分是不可用的.
父进程, 也就是脚本本身, 将不能够读取在子进程中创建的变量, 也就是在子shell中创建的变量.

(cmd1;cmd2;cmd3)

②命令替换。命令替换(cmd)等同于`cmd`(这不是单引号,`是ESC下面的那个键) ,shell执行过程中发现了(cmd)结构,便将$(cmd)中的cmd执行一次,得到其输出,再将此输出放到原来命令。例如:

[root@localhost tmp]# ls
fstab  functions  hellobash  issue  mytestdir  scripts
[root@localhost tmp]# echo $(ls)
fstab functions hellobash issue mytestdir scripts
[root@localhost tmp]# echo `ls`
fstab functions hellobash issue mytestdir scripts

③用于初始化数组。如:arr=(m n)

大括号{}

①拓展。对大括号中的文件名做扩展。在大括号中,不允许有空白,除非这个空白被引用或转义。拓展分为普通以逗号(,)进行拓展,如echo {a,b}.txt将间隔的各项内容均列出;已两个点(..)进行拓展,如echo {1..5}.txt自动补全1到5中间内容。

[root@localhost ~]# echo {a,b}.txt
a.txt b.txt
[root@localhost ~]# echo {1..5}.txt
1.txt 2.txt 3.txt 4.txt 5.txt

②内部组 。与小括号中的命令不同,大括号内的命令在当前shell运行,不会重新开子shell。
括号内的命令间用分号隔开,最后一个命令后必须跟分号。{}的第一个命令和左括号之间必须要有一个空格。

双小括号 (( ))

  • (( ... ))结构可以用来计算并测试算术表达式的结果. 退出状态将会与[ ... ]结构完全相反!还可应用到c风格的for,while循环语句,(( )) 中,所有的变量(加不加$无所谓)都是数值。
  • $((...))结构的表达式是C风格的表达式,其返回的结果是表达式值,其中变量引用可不用‘$’(当然也可以)
for((...;...;...))
do
  cmd
done
while ((...))
do
  cmd
done

其他小知识点

  • 获取进程号:
ps|grep "${pname}" |grep -v "grep"|grep -v grep|awk '{print $1}' > "supertack.pid"
- 如果是2个pid,则此时文件supertack.pid中会保存为:
1234
1235
- 可以通过如下方式将2行内容输出到同一行,并以空格分隔开:
fpid=$(echo $(cat ${supertack_pid}))
echo $fpid  
输出为:1234 1235
然后在 kill -9 $fpid
  • 本地计数
    需求是这样的,我想在本地这个shell脚本,在第一次执行和第二十次执行的时候,在做一件事情;
    如下方法大致逻辑为:
    • 方法启动就执行一次
    • 方法每被调用一次就累加一次执行记录,并存储在文件中
    • 方法在第二十次执行的时候,将存储文件中记录次数初始化。
tick_times="a_ticktack_times.log"
ticktimes=20
control_tick(){
    [ -s "${tick_times}" ] && { ticktimes=$(cat ${tick_times});}
    [ -z "${ticktimes}" ] &&  { echo "1">${tick_times};ticktimes=1;}
    debug_print "control_tick ticktimes=$ticktimes"
    if [ `expr $ticktimes % 20` == 0  ];then
        echo "1">${tick_times}
        return 8;
    else
        ticktimes=$(expr $ticktimes + 1);
        echo "${ticktimes}">${tick_times}
        return 7;
    fi
}
work(){
   control_tick
   is_tick=$?
   [ "${is_tick}" = 7 ] && {   echo  "TICK WAIT ,is_tick=fase[$is_tick]" && return 1 ; }
  do something;
}
  • 解析一维json字符串
f_jparse() {
    echo $1 | \
    sed -e 's/[{}]//g' | \
    sed -e 's/", "/","/g' | \
    sed -e 's/" ,"/","/g' | \
    sed -e 's/" , "/","/g' | \
    sed -e 's/","/'\"---RECORDSEPERATOR---\"'/g' | \
    awk -v RS='---RECORDSEPERATOR---' "\$1~/^\"$2\"/ {print}" | \
    sed -e "s/\"$2\"://"| sed 's/\"//g'|sed -e 's/\(^ *\)//' -e 's/\( *$\)//'
}
json={
  'type':1,
  'name':123
}
xurl=$(f_jparse "$json" 'type')
echo $xurl
输出:1

目前项目中只用到这些知识点,以后遇到在总结,😋

推荐阅读更多精彩内容