shell编程 2022-09-06

2022-09-05  本文已影响0人  9_SooHyun

shell哲学

shell中一切皆为表达式
shell没有数据类型的概念,所有的变量值本质上都以字符串进行存储
The shell does not care about types of variables' data; they may store strings, integers, real numbers - anything you like
In truth, these are all stored as strings, but routines which expect a number, a boolean, etc can treat them as such.
至于具体的类型,Shell解释器会根据上下文去确定。eg1.

#!/bin/sh
# RESULT是个字符串
RESULT="false"
# RESULT此时为bool
if $RESULT; then
    echo "Is true."
else
    echo "Is false."
fi

eg2.

[root@VM-165-116-centos ~]# a="0"                              
[root@VM-165-116-centos ~]# if [ "$a" != 0 ]; then echo hhh; fi 
[root@VM-165-116-centos ~]# if [ "$a" == 0 ]; then echo hhh; fi
hhh
[root@VM-165-116-centos ~]# if [ "$a" -eq 0 ]; then echo hhh; fi
hhh
[root@VM-165-116-centos ~]# 
[root@VM-165-116-centos ~]# ((a++))
[root@VM-165-116-centos ~]# echo $a
1

sh在解析字符的时候,“;” 等同 回车

根据表达式的定义,任何表达式都必须有一个值。因此shell一切皆表达式的设计原则,确定了shell在执行任何东西(注意是任何东西,不仅是命令)的时候都会有一个返回值。在shell编程中,这个返回值也限定了取值范围:0-255。0为真(true),非0为假(false)。例如,我们可以使用关键字$?来查看上一个执行命令的返回值

➜  ~ echo hhh
hhh
➜  ~ echo $?
0
➜  ~

Accessing Values

We can access the value stored in a variable by prefixing its name with the dollar sign $. When the shell sees a $, it performs the following actions:

基本语法

quote

Strong Quoting with the Single Quotes

When you need to quote several character at once, you could use several backslashes:

% echo a\ \ \ \ \ \ \ b

(There are 7 spaces between 'a' and 'b'.) This is ugly but works. It is easier to use pairs of quotation marks to indicate the start and end of the characters to be quoted:

% echo 'a       b'

Inside the single quotes, you can include almost all meta-characters:

% echo 'What the *heck* is a $ doing here???'
What the *heck* is a $ doing here???

The above example uses asterisks, dollar signs, and question marks meta-characters. The single quotes should be used when you want the text left alone. Note - If you are using the C shell, the "!" character may need a backslash before it. It depends on the characters next to it. If it is surrounded by spaces, you don't need to use a backslash.

strong quotes 所见即所得,像golang的`xx`,python的```xx```

Weak Quotes with the Double Quotes

Sometimes you want a weaker type of quoting: one that doesn't expand meta-characters like "*" or "?," but does expand variables and does command substitution. This can be done with the double quote characters:

% echo "Is your home directory $HOME?"
Is your home directory /home/barnett?
% echo "Your current directory is `pwd`"
Your current directory is /home/barnett
# This next example won't work in the C shell and Bourne shell
% echo "Your current directory is $(pwd)"
Your current directory is /home/barnett

字符串

比较

对比字符串只能使用==、<、>、!=、-z、-n
使用[ == ]对比字符串时,需要在首尾附加额外字符以避免报错。如if [ "1"x == "ab"x ]没有了x ,并且1是"",会翻译成if [ == "ab" ],会报语法错误。或者使用[[ ]],就不需要x了。
使用<或者>时,如果是用[ ],需要用转义符"",如>
-z, is_zero,判断长度为0
-n, 判断长度不为0

截取,替换

${strvar:startindex:lenth} 截取
${strvar/substring/replacement} 使用replacement, 来代替第一个匹配的substring
${strvar//substring/replacement} 使用replacement, 代替所有匹配的substring

数字

比较

对比数字使用既能使用-eq、-ne、-gt、-ge、-lt、-le,也能使用==、<、>、!=

算数运算

最简单的做法是,把符合C语言语法的表达式放到(())中。这时,(())中的变量无需带上$

➜  ~ echo $((1+2))
3
➜  ~ ((i=1+3))
➜  ~ echo $i
4
➜  ~ ((i++))
➜  ~ echo $i
5
➜  ~

if 分支结构

if list; then list; elif list; then list; ... else list; fi

其中,list是若干个使用管道,;&&&||这些符号串联起来的shell命令序列
可以简单的理解为,if后面跟的就是个shell命令

if语法中后面最常用的命令是[]。请注意,[]是一个命令——test命令(有对应的二进制文件),也就是说[]类似test的语法糖。这也是shell编程容易犯错的地方之一,如果在一开始接触shell编程的时将[]当成if语句的语法结构而不是命令,写[]的时候就经常会里面不写空格,即:

# 正确的写法
if [ $ret -eq 0 ]
# 错读的写法
if [$ret -eq 0]

# 另外,以下写法等价
if [ $ret -eq 0 ] 
if test $ret -eq 0

test expression可以进行数值、字符和文件三个方面的测试
test 判断 expression 成立时,退出状态为 0,否则为非 0 值
test命令也可以简写为[],它的语法为[ expression ]

while/until 循环结构

   while list-1; do list-2; done
   until list-1; do list-2; done

以下脚本可用于检测目标机器是否ping通

#!/bin/sh

IPADDR='8.8.8.8'
INTERVAL=1

while true
do
    # if ping failed, the command `ping -c 1 $IPADDR &> /dev/null` return 1
    while ping -c 1 $IPADDR &> /dev/null
    do
        echo "$IPADDR ping ok!"
        sleep $INTERVAL
    done

    echo "$IPADDR ping error! " 1>&2

    # loop sleep until ping succeed
    until ping -c 1 $IPADDR &> /dev/null
    do
        sleep $INTERVAL
    done

    echo "$IPADDR ping ok!"
done

case 分支结构

case word in [ [(] pattern [ | pattern ] ... ) list ;; ] ... esac

eg.

case $1 in
        pattern)
        list
        ;;
        pattern)
        list
        ;;

        ...
esac

for 循环结构

for name [ [ in [ word ... ] ] ; ] do list ; done

eg.

➜  ~ for i in 1 2 3; do echo $i ; done
1
2
3
➜  ~ a=(1 2 3) && for i in ${a[@]}; do echo $i ; done
1
2
3
➜  ~

利用@或*,可以将数组扩展成列表
然后使用#来获取数组元素的个数,格式如下:

${#array_name[@]}
${#array_name[*]}
两种形式是等价的

获取命令行/脚本的入参

使用getopt

getopt是一个外部命令,支持短选项和长选项

Each short/long option character in shortopts may be followed by one colon to indicate it has a required argument, and by two colons to indicate it has an optional argument.

A simple short option is a '-' followed by a short option character.

A long option normally begins with '--' followed by the long option name.

#!/bin/sh

# 处理参数,规范化参数。:表示该opt需要parameter
ARGS=`getopt -o a:s: --long agentnames:,serverips:,idcfuncname: -- "$@"`
if [ $? != 0 ];then
    echo "Parse args err. Terminating..."
    exit 1
fi
# 重新排列参数顺序,组织成($1, $2, ...)
eval set -- "${ARGS}"
# 通过shift和while循环处理参数
while true
do
    # $1 表示 option 名,$2 表示option的value
    case $1 in
        # 安装的目标agent
        -a | --agentnames)
            agent_list=($2)
            shift
            ;;

        # server ip,包含主备两个
        -s | --serverips)
            # should contain a server master ip and a server slave ip
            server_ips=($2)
            num=${#server_ips[@]}
            if [ ${num} -ne 2 ]; then 
                echo "the given server ip num is ${num}, not eq 2"
                exit 1
            fi
            # 使用环境变量 SERVER_IP 配置 server ip
            export SERVER_IP="$2"
            shift
            ;;
        
        # 不同的功能类型可能需要前置执行不同的策略
        --idcfuncname)
            idcfuncname=$2
            case ${idcfuncname} in 
                ABC )
                    echo ${idcfuncname}
                    ;;
            esac
            shift
            ;;
       
        --)
            shift
            break
            ;;
        *)
            echo "Internal error!"
            exit 1
            ;;
    esac
shift
done
# echo "agent_list: ${agent_list[@]}"
# echo "server_ips: ${SERVER_IP}"
# echo "idcfuncname: ${idcfuncname}"

字符串运算

替换
# string中第一个old替换为new
${string/old/new} 

# string中所有old替换为new
${string//old/new}

# string从下标n到结尾的子串
${string:n} 

# string从下标n开始长度为m的子串
${string:n:m}   

# string从下标0开始长度为m的子串
${string::m}    

数组

普通数组

a=()         # 空数组
a=(1 2 3)    # 元素为1,2,3的数组
echo ${#a[*]}  # 数组长度
echo ${a[2]}   # 下标为2的元素值(下标从0开始)
a[1]=0         # 给下标为1的元素赋值

# 遍历数组
for i in ${a[*]}
do
    echo ${i}
done

unset a        # 清空数组

换行符处理

如果命令执行结果有多行内容,存入变量并打印时换行符会丢失。给echo 加上引号能够保持原样

[root@VM-centos ~]# echo $(free)  
total used free shared buff/cache available Mem: 16132456 3998044 1187960 296480 10946452 11708584 Swap: 0 0 0
[root@VM-centos ~]# echo "$(free)"
              total        used        free      shared  buff/cache   available
Mem:       16132456     3987904     1193392      296480    10951160    11707856
Swap:             0           0           0
[root@VM-165-116-centos ~]# 

并发

使用&wait可以实现并发执行任务
wait is a BASH built-in command. From man bash:

    wait [n ...]
        Wait  for each specified process and return its termination sta-
        tus.  Each n may be a process ID or a job  specification;  if  a
        job  spec  is  given,  all  processes in that job's pipeline are
        waited for.  If n is not given, all currently active child  pro-
        cesses  are  waited  for,  and  the return status is zero.  If n
        specifies a non-existent process or job, the  return  status  is
        127.   Otherwise,  the  return  status is the exit status of the
        last process or job waited for.

eg.

workhard &
[1] 27408
workharder &
[2] 27409
wait %1 %2 (or wait, or wait 27408 27409)

wait waits for the child process of the current shell to be done

https://stackoverflow.com/questions/13296863/difference-between-wait-and-sleep

nohup 和 & 的区别

&使得命令在后台以子进程方式运行,但是一旦当前shell terminated,所有的这些子进程也会挂,因为它们会收到当前shell发来的SIGHUP信号。而nohup可以捕捉SIGHUP signal,这样这些子进程就永远收不到SIGHUP,即使当前shell terminated它们也一无所知地正常运行下去

& would run the process in background using a subshell. If the current shell is terminated (say by logout), all subshells are also terminated so the background process would also be terminated.

when running a command using & and exiting the shell afterwards, the shell will terminate the sub-command with the hangup signal (kill -SIGHUP <pid>). This can be prevented using nohup, as it catches the signal and ignores it so that it never reaches the actual application.

In case you're using bash, you can use the command shopt | grep hupon to find out whether your shell sends SIGHUP to its child processes or not. If it is off, processes won't be terminated, which means sub-shells can still be alive while shell is terminated

refers: https://stackoverflow.com/questions/15595374/whats-the-difference-between-nohup-and-ampersand

上一篇下一篇

猜你喜欢

热点阅读