Shell脚本入门

一、基本代码

#! /bin/bash
num=`ps -ef |grep zookeeper | grep -v grep | wc -l`

# 判断进程是否存在,如果不存在则启动,否则输出Running
if [ $num -lt 1 ];then
	su - peng -c "/Users/peng/data/apache-zookeeper-3.6.2-1/bin/zkServer.sh start"
else
	echo "It's already running"
fi

注释以#开头,只支持单行注释。

1.1 变量定义

变量名与等号之间不能有空格,只能使用字母、数字、下划线,不能以数字开头,不能使用bash里的关键字。

1.2 if语句

注意,条件判断方括号与变量以及变量与运算符之间需要有空格thenif可以换行,放一行需要用分号分隔,最后以fi结束,如果要跟多个elif的示例:

#! /bin/bash

idx=10

if [ $idx -gt 3 ]
then 
    echo "$idx > 3"
elif [ $idx -gt 2 ]; then echo "$idx > 2"; elif [ $idx -gt 1 ]; then
    echo "${idx} > 1"
else
    echo "${idx} <= 1"
fi

1.3 反引号

执行命令并将命令的输出赋值给变量num,反引号的作用和$(命令)是一样的,借助该功能就实现可以强大的外部程序调用。

num=$(ps -ef |grep zookeeper | grep -v grep | wc -l)

1.4 数字比较

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。

运算符 说明 举例
-eq 检测两个数是否相等,相等返回 true。 [ $a -eq $b ] 返回 true。
-ne 检测两个数是否相等,不相等返回 true。 [ $a -ne $b ] 返回 true。
-gt 检测左边的数是否大于右边的,如果是,则返回 true。 [ $a -gt $b ] 返回 false。
-lt 检测左边的数是否小于右边的,如果是,则返回 true。 [ $a -lt $b ] 返回 true。
-ge 检测左边的数是否大等于右边的,如果是,则返回 true。 [ $a -ge $b ] 返回 false。
-le 检测左边的数是否小于等于右边的,如果是,则返回 true。 [ $a -le $b ] 返回 true。

1.5 字符串比较

运算符 说明 举例
= 检测两个字符串是否相等,相等返回 true。 [ $a = $b ] 返回 false。
!= 检测两个字符串是否相等,不相等返回 true。 [ $a != $b ]返回 true。
-z 检测字符串长度是否为0,为0返回 true。 [ -z $a ] 返回 false。
-n 检测字符串长度是否为0,不为0返回 true。 [ -n $a ] 返回 true。
str 检测字符串是否为空,不为空返回 true。 [ $a ] 返回 true。

字符串比较如果是使用单中括号最好在字符串上添加双引号,防止字符串为空时报语法错误,使用双中括号可以避免语法错误。

[ "$a" == "$b" ] && echo "=="
[[ $a == $b ]] && echo "=="

关于等号,在这里使用单个等号或者两个等都都是可以的。但双中括号可以用来做模糊匹配,如下以prefix开头的字符串:

[[ $a == prefix* ]] && echo "=="

1.6 布尔运算符

运算符 说明 举例
! 非运算,表达式为 true 则返回 false,否则返回 true。 [ ! false ] 返回 true。
-o 或运算,有一个表达式为 true 则返回 true。 [ $a -lt 20 -o $b -gt 100 ]返回 true。
-a 与运算,两个表达式都为 true 才返回 true。 [ $a -lt 20 -a $b -gt 100 ] 返回 false。

注:示例中多个条件为一个中括号

结合前面的数字比较,有多个条件则可以:

[ $a -eq 10 -a $b -eq 20 ] && echo "=="
[ "$c" == "c" -a "$d" == "d" ] && echo "=="

1.7 逻辑运算符

假定变量 a 为 10,变量 b 为 20:

运算符 说明 举例
&& 逻辑的 AND [[ $a -lt 100 && $b -gt 100 ]] 返回 false
|| 逻辑的 OR

注:示例中多个条件为2个中括号

这里有两种写法,在[[中使用&&||表示逻辑与和逻辑或。[中使用-a-o 表示逻辑与和逻辑或。

if [[ $a < 20 || $b > 100 ]]; then
    echo "$a < 20 or $b > 100"
fi


if [ $a -lt 20 -o $b -gt 100 ]; then
    echo "$a < 20 or $b > 100"
fi

当然还可以把条件进行拆分,&&|| 的优先级是相同的。

[ $a -lt 20 ] && [ $b -gt 100 ] && echo "$a < 20 and $b > 100"

1.8 文件判断

使用不同的条件标志测试不同的文件系统属性。

操作符 意义
[ -f $file_var ] 变量 $file_var 是一个正常的文件路径或文件名 (file),则返回真
[ -x $var ] 变量 $var 包含的文件可执行 (execute),则返回真
[ -d $var ] 变量 $var 包含的文件是目录 (directory),则返回真
[ -e $var ] 变量 $var 包含的文件存在 (exist),则返回真
[ -c $var ] 变量 $var 包含的文件是一个字符设备文件的路径 (character),则返回真
[ -b $var ] 变量 $var 包含的文件是一个块设备文件的路径 (block),则返回真
[ -w $var ] 变量 $var 包含的文件可写(write),则返回真
[ -r $var ] 变量 $var 包含的文件可读 (read),则返回真
[ -L $var ] 变量 $var 包含是一个符号链接 (link),则返回真

使用方法如下:

[ -e "/etc/passwd" ] && {
  echo File exits;
} || {
  echo Does not exit;
}

1.9 特殊参数

$1为启动脚本时跟着脚本后面的第一个参数。

变量 含义
$0 当前脚本的文件名
$n 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2
$# 传递给脚本或函数的参数个数。
$* 传递给脚本或函数的所有参数。
$@ 传递给脚本或函数的所有参数。被双引号(" ")包含时,与 $* 稍有不同,下面将会讲到。
$? 上个命令的退出状态,或函数的返回值。
$$ 当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。
#!/bin/bash
echo "File Name: $0"
echo "First Parameter : $1"
echo "First Parameter : $2"
echo "Quoted Values: $@"
echo "Quoted Values: $*"
echo "Total Number of Parameters : $#"

执行后输出:

$ ./1.sh -host 127.0.0.1 -port 80
File Name: ./1.sh
First Parameter : -host
First Parameter : 127.0.0.1
Quoted Values: -host 127.0.0.1 -port 80
Quoted Values: -host 127.0.0.1 -port 80
Total Number of Parameters : 4

$*$@ 的区别

$*$@ 都表示传递给函数或脚本的所有参数,不被双引号(" ")包含时,都以"$1" "$2""$n" 的形式输出所有参数。

但是当它们被双引号(" ")包含时,"$*" 会将所有的参数作为一个整体,以"$1 $2 … $n"的形式输出所有参数;"$@" 会将各个参数分开,以"$1" "$2""$n" 的形式输出所有参数。

注意:函数参数传递也是通过这些特殊变量

二、代码块

3.1 Case语句

常用脚本中根据传入参数做启动与重启。

#! /bin/bash

case "$1" in
  start)
    echo "start process..."
    ;;
  stop)
    echo "stop process..."
    ;;
  *)
    echo $"Usage: $0 {start|stop}"
esac

看示例用法,;;相当于break跳出case语句。

3.2 for循环

#! /bin/bash

for loop in 1 2 3 4 5
do 
    echo "The value is $loop"
done

params="
  1
  2
  3
  4
  5
"

for loop in $params
do
    echo "The value is $loop"
done

in后的内容是一组值(数字、字符串等)组成的序列,每个值通过空格分隔。每循环一次,就将列表中的下一个值赋给变量。循环内部也可以使用breakcontinue跳出循环。

3.2 while循环

服务发布过程中判断服务进程是否已退出,如果没有退出则等待30s。

#! /bin/bash

# 重启服务过程中查看进程是否已经关闭
count=$(ps -ef | grep elasticsearch | grep -v grep | wc -l)
times=1
while [ $count -gt 0 -a $times -lt 30 ]; do
    echo "count: $count, times: $times"
    sleep 1
    let times++
    count=$(ps -ef | grep elasticsearch | grep -v grep | wc -l)
done

if [ $times -ge 30 ]; then
    echo "process closed failed"
    exit 1
fi

echo "restart program"

let times++可以实现自增,也可以写成:

times=`expr $times + 1`

四、函数

#! /bin/bash

function start() {
    echo "Start: $1 $2"
}

stop() {
    echo "Stop: $1"
    return 0
}

case "$1" in
  start)
    start $2 $3
    ;;
  stop)
    stop $2
    ;;
  *)
    echo $"Usage: $0 {start|stop}"
esac

调用示例:

pengbotao:Desktop peng$ ./1.sh start app 01
Start: app 01
pengbotao:Desktop peng$ ./1.sh stop app 01
Stop: app

五、字符串与数组

5.1 字符串

#! /bin/bash

str1='Hello, $1'
str2="Hello $1"

echo $str1
echo $str2

执行后可以看到:

$ ./1.sh World
Hello, $1
Hello World

5.2 字符串拼接

echo $str1 $str2

5.3 字符串长度

string="abcd"
echo ${#string} #输出 4

5.4 提取子字符串

string="alibaba is a great company"
echo ${string:1:4} #输出liba

5.5 数组

#! /bin/bash

im=(qq weixin)

# 查看元素内容
echo ${im[0]}
# 修改或新增元素
im[1]="dingding"
echo ${im[1]}

5.6 获取数组长度

echo ${#im[*]}
echo ${#im[@]}

5.7 遍历数组

#! /bin/bash

im=(qq weixin)

for loop in ${im[*]}
# for loop in ${im[@]}
do 
echo $loop
done

六、输入输出重定向

Unix命令默认从标准输入设备(stdin)获取输入,将结果输出到标准输出设备(stdout)显示。一般情况下,标准输入设备就是键盘,标准输出设备就是终端,即显示器。一般情况下,每个Unix/Linux命令运行时都会打开三个文件:

6.1 输出重定向

将标准输出内容重定向到文件,> 执行会覆盖原文件内容,>>会以追加的方式写到文件。

$ command > file

如果希望将stderr重定向到file文件:

$ command 2 > file

如果希望将 stdout 和 stderr 合并后重定向到 file,可以这样写:/data/shell/backup.sh > /dev/null 2>&1

$ command > file 2>&1

以追加的方式将内容写到文件,只是多了一个>

$ command >> file

6.2 输入重定向

$ command < file

6.3 Here Document

$ command << delimiter
    document
delimiter

它的作用是将两个 delimiter 之间的内容(document) 作为输入传递给 command。注意:

示例(可以保持多行的输入):

$ cat << EOF | curl -X PUT -H "Content-type: application/json" 'http://localhost:9200/demo?pretty' -d @- 
{
    "mappings": {
        "_doc": {
            "properties": {
                "title": {
                    "type" : "keyword"
                },
                "content": {
                    "type": "text"
                }
            }
        }
    }
}
EOF

6.4 /dev/null文件

如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到/dev/null

$ command > /dev/null

/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是/dev/null 文件非常有用,将命令的输出重定向到它,会起到”禁止输出“的效果。

如果希望屏蔽stdoutstderr,可以这样写:

$ command > /dev/null 2>&1

七、代码片段

7.1 循环处理

#! /bin/bash

output_file="./export/test.data"

> "$output_file"

for idx in {0..100}; do
    echo "start export idx:$idx" >> "$output_file"
done

echo "output: $output_file"

7.2 读取文件

#!/bin/sh

if [ "$#" -ne 1 ]; then
    echo "Usage: $0 <file_path>"
    exit 1
fi

file_path="$1"

while IFS= read -r line || [ -n "$line" ]; do
  echo "$line"
done < "$file_path"

7.3 按函数执行

#!/bin/bash

check_keyword() {
    local filename="$1"
    local keyword="$2"

    tmpdir="./data/tmp"
    if [ ! -d "$tmpdir" ]; then
        mkdir -p "$tmpdir"
    fi

    while IFS= read -r file || [ -n "$file" ]; do
        output="${tmpdir}/${file}_search.txt"
        > "$output"
        grep "$keyword" $file >> "$output"
    done < "$filename"
}

if [ "$#" -ne 2 ]; then
    echo "Usage: $0 <filename> <keyword>"
    exit 1
fi

# 调用函数,并传递参数
check_keyword "$1" "$2"

7.4 多个函数

#!/bin/bash

main() {
    if [ "$#" -ne 2 ]; then
        echo "Usage: $0 <source> <keyword>"
        exit 1
    fi

    local source="$1"
    local keyword="$2"

    case "$source" in
        "1")
            process_1
            ;;
        "2")
            process_2
            ;;
        *)
            echo "Invalid source: $source"
            exit 1
            ;;
    esac
}


process_1() {
   echo "$keyword"
}

process_2() {
   :
}


# 调用主函数
main "$@"
-- EOF --
最后更新于: 2024-08-17 14:44
发表于: 2018-10-01 21:24
标签: Shell 运维