Jiarui's Blog.

Shell Scripts Notes

2018/05/11 Share

脚本文件开头

在每个bash脚本的开头都使用#!,这用来告诉系统此文件的执行需要指定一个解释器。#!实际上是一个 2 字节的魔力数字,这是指定一个文件类型的特殊标记,换句话说, 在这里指是一个可执行的脚本(键入 man magic 来获得关于这个迷人话题的更多详细信息)。在#!之后接着是一个路径名,这个路径名指定了一个解释脚本中命令的程序,这个程序可以是 shell,其它编程语言或任意一个通用程序。这个指定的程序从头开始解释并且执行脚本中的命令(从#!行下边的一行开始),忽略注释。

如:

1
2
3
4
5
6
#!/bin/sh
#!/bin/bash
#!/usr/bin/perl
#!/usr/bin/tcl
#!/bin/sed -f
#!/usr/awk -f

上边每一个脚本头的行都指定了一个命令解释器,如果是/bin/sh,那么就是默认shell(在 Linux 系统中默认是 Bash)。使用#!/bin/sh,在大多数商业发行的 UNIX 上,默认是 Bourne shell,这将让你的脚本可以正常的运行在非 Linux 机器上,虽然这将会牺牲 Bash 一些独特的特征。脚本将与 POSIX 的 sh 标准相一致。

注意: #! 后边给出的路径名必须是正确的,否则将会出现一个错误消息,通常是"Command not found",这将是你运行这个脚本时所得到的唯一结果。当然 #! 也可以被忽略,不过这样你的脚本文件就只能是一些命令的集合,不能够使用 shell 内建的指令了。

脚本中的#!行的最重要的任务就是命令解释器(sh 或者 bash)。因为这行是以#开始的,当命令解释器执行这个脚本的时候,会把它作为一个注释行。当然,在这之前,这行语句已经完成了它的任务,就是调用命令解释器。

注:那些具有 UNIX 味道的脚本(基于 4.2BSD)需要一个 4 字节的魔法数字,在#!后边需要一个空格#! /bin/sh

转载自:

Shell 传递参数

我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$nn 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……

实例

以下实例我们向脚本传递三个参数,并分别输出,其中 $0 为执行的文件名。

脚本文件命名为test.sh,内容如下:

1
2
3
4
5
6
7
8
9
#!/bin/bash
# author:菜鸟教程
# url:www.runoob.com

echo "Shell 传递参数实例!";
echo "执行的文件名:$0";
echo "第一个参数为:$1";
echo "第二个参数为:$2";
echo "第三个参数为:$3";

为脚本设置可执行权限,并执行脚本,输出结果如下所示:

1
2
3
4
5
6
7
$ chmod +x test.sh 
$ ./test.sh 1 2 3
Shell 传递参数实例!
执行的文件名:./test.sh
第一个参数为:1
第二个参数为:2
第三个参数为:3

在Shell中,脚本名称本身是$0,我们这里就是“./test.sh”,剩下的依次是$1, $2,...,${10}, ${11},等等。$#表示包括$0在内的命令行参数的个数。$*表示整个参数列表,不包括$0,也就是说不包括文件名的参数列表。

处理参数的特殊字符

参数处理 说明
$# 表示包括$0在内的命令行参数的个数
$* 表示整个参数列表,不包括$0,也就是说不包括文件名的参数列表。如"$*""括起来的情况、以"$1 $2 … $n"的形式输出所有参数。
$$ 脚本运行的当前进程ID号
$! 后台运行的最后一个进程的ID号
$@ $*相同,但是使用时加引号,并在引号中返回每个参数。如"$@""括起来的情况、以"$1" "$2" … "$n" 的形式输出所有参数。
$- 显示Shell使用的当前选项,与set命令功能相同。
$? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。

$*$@ 区别:

  • 相同点:都是引用所有参数。

  • 不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,则 " * " 等价于 "1 2 3"(传递了一个参数),而 "@" 等价于 "1" "2" "3"(传递了三个参数)。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
# author:菜鸟教程
# url:www.runoob.com

echo "-- \$* 演示 ---"
for i in "$*"; do
echo $i
done

echo "-- \$@ 演示 ---"
for i in "$@"; do
echo $i
done

执行脚本,输出结果如下所示:

1
2
3
4
5
6
7
8
$ chmod +x test.sh 
$ ./test.sh 1 2 3
-- $* 演示 ---
1 2 3
-- $@ 演示 ---
1
2
3

注意事项

  • 在为shell脚本传递的参数中如果包含空格,应该使用单引号或者双引号将该参数括起来,以便于脚本将这个参数作为整体来接收

  • 在有参数时,可以使用对参数进行校验的方式处理以减少错误发生:

1
2
3
4
5
if [ -n "$1" ]; then
echo "包含第一个参数"
else
echo "没有包含第一参数"
fi
  • 中括号 [] 与其中间的代码应该有空格隔开

转载自:

Shell 赋值

赋值记得等号前后不能有空格。

1
2
3
4
5
6
#!/bin/bash

A=1
export A

echo " A = $A.\n "

Shell 脚本的if语句

if语法

if语句必须以单词fi终止。在if语句中漏写fi是最一般的错误。我自己有时也是这样。elif和else为可选项,如果语句中没有否则部分,那么就不需要elif和else部分。if语句可以有许多elif部分。

1
2
3
4
5
6
7
if  [ condition ]   --注意括号两边有空格,condition 是个条件表达式
then commands
elif [ condition ]
then commands
else
commands
fi

或者:

1
2
3
4
5
6
7
if  [ condition ]; then  
commands
elif [ condition ]; then
commands
else
commands
fi

例如:

1
2
3
4
5
6
7
if [ $a -gt $b ]
then echo "a大于b"
fi

if [ $1 = city ]; then
echo "city"
fi

if后的condition一定要是一个条件语句,其结果应该是truefalse,虽然我们常常将1认为是true0认为是false,但是这里的condition运算结果只能是truefalse,否则,即使执行结果是10,都会认为condition这个条件是具备的,就不走其他分支了。

条件判断语句

  • 条件测试的表达式:

    [ expression ] 括号两端必须要有空格

    [[ expression ]] 括号两端必须要有空格

  • 组合测试条件:

    • -a: and
    • -o: or
    • !: 非
  • 整数比较:

    • -eq 测试两个整数是否相等
    • -ne 测试两个整数是否不等
    • -gt 测试一个数是否大于另一个数
    • -lt 测试一个数是否小于另一个数
    • -ge 大于或等于
    • -le 小于或等于
  • 命令间的逻辑关系

    • 逻辑与:&&
    • 逻辑或:||
1
2
if [ condition1 -a condition2 ] 或 if [ condition1 ] && [ condition2 ]
if [ condition1 -o condition2 ] 或 if [ condition1 ] || [ condition2 ]
  • 字符串比较
    • == 等于 两边要有空格
    • != 不等
    • > 大于
    • < 小于
  • 文件测试
    • -z string 测试指定字符是否为空,空着真,非空为假
    • -n string 测试指定字符串是否为不空,空为假 非空为真
    • -e FILE 测试文件是否存在
    • -f file 测试文件是否为普通文件
    • -d file 测试指定路径是否为目录
    • -r file 测试文件对当前用户是否可读
    • -w file 测试文件对当前用户是否可写
    • -x file 测试文件对当前用户是都可执行
    • -z 是否为空 为空则为真
    • -a 是否不空

If语句进行判断是否为空:

[ "$name” = "" ] 等同于 [ ! "$name" ][ -z "$name" ]

示例脚本

写一段脚本,输入一个测验成绩,根据下面的标准,输出他的评分成绩(A-F)

A: 90–100

B: 80–89

C: 70–79

D: 60–69

F: <60

脚本文件命名为grade.sh,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#/bin/bash
#Verson:0.1
#Auther:lovelace
#Pragram:This pragram is calculation your grade
#import an argument
read -p "Please input your grade:" x
declare -i x
#jugemet $x value is none or not
if [ "$x" == "" ];then
echo "You don't input your grade...."
exit 5
fi
#jugement the gread level
if [[ "$x" -ge "90" && "$x" -le "100" ]];then
echo "Congratulation,Your grade is A."
elif [[ "$x" -ge "80" && "$x" -le "89" ]];then
echo "Good,Your grade is B."
elif [[ "$x" -ge "70" && "$x" -le "79" ]];then
echo "Ok.Your grade is C."
elif [[ "$x" -ge "60" && "$x" -le "69" ]];then
echo "Yeah,Your grade is D."
elif [[ "$x" -lt "60" ]];then
echo "Right,Your grade is F."
else
echo "Unknow argument...."
fi

执行结果:

1
2
3
4
5
6
7
8
9
[root@lovelace if]# ./grade.sh
Please input your grade:
You don't input your grade....
[root@lovelace if]# ./grade.sh
Please input your grade:53
Right,Your grade is F.
[root@lovelace if]# ./grade.sh
Please input your grade:98
Good,Your grade is A.

转载自

在 Shell 脚本中调用另一个 Shell 脚本的三种方式

先来说一下主要以下有几种方式:

  • fork: 如果脚本有执行权限的话,path/to/foo.sh。如果没有,sh path/to/foo.sh

  • exec: exec path/to/foo.sh

  • source: source path/to/foo.sh

fork

fork 是最普通的, 就是直接在脚本里面用 path/to/foo.sh 来调用 foo.sh 这个脚本,比如如果是 foo.sh 在当前目录下,就是 ./foo.sh。运行的时候 terminal 会新开一个子 Shell 执行脚本 foo.sh,子 Shell 执行的时候, 父 Shell 还在。子 Shell 执行完毕后返回父 Shell。 子 Shell 从父 Shell 继承环境变量,但是子 Shell 中的环境变量不会带回父 Shell。

exec

exec 与 fork 不同,不需要新开一个子 Shell 来执行被调用的脚本. 被调用的脚本与父脚本在同一个 Shell 内执行。但是使用 exec 调用一个新脚本以后, 父脚本中 exec 行之后的内容就不会再执行了。这是 exec 和 source 的区别.

source

与 fork 的区别是不新开一个子 Shell 来执行被调用的脚本,而是在同一个 Shell 中执行. 所以被调用的脚本中声明的变量和环境变量, 都可以在主脚本中进行获取和使用。

例子

其实从命名上可以感知到其中的细微区别,下面通过两个脚本来体会三种调用方式的不同:

第一个脚本,我们命名为 1.sh:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env bash

A=1

echo "before exec/source/fork: PID for 1.sh = $$"

export A
echo "In 1.sh: variable A=$A"

case $1 in
--exec)
echo -e "==> using exec…\n"
exec ./2.sh ;;
--source)
echo -e "==> using source…\n"
. ./2.sh ;;
*)
echo -e "==> using fork by default…\n"
./2.sh ;;
esac

echo "after exec/source/fork: PID for 1.sh = $$"
echo -e "In 1.sh: variable A=$A\n"

第二个脚本,我们命名为 2.sh

1
2
3
4
5
6
7
8
9
#!/usr/bin/env bash

echo "PID for 2.sh = $$"
echo "In 2.sh get variable A=$A from 1.sh"

A=2
export A

echo -e "In 2.sh: variable A=$A\n"

注:这两个脚本中的参数 $$ 用于返回脚本的 PID , 也就是进程 ID。这个例子是想通过显示 PID 判断两个脚本是分开执行还是同一进程里执行,也就是是否有新开子 Shell。当执行完脚本 2.sh 后,脚本 1.sh 后面的内容是否还执行。

chmod +x 1.sh 2.sh 给两个脚本加上可执行权限后执行情况:

fork

fork.png

fork 方式可以看出,两个脚本都执行了,运行顺序为1-2-1,从两者的PID值(1.sh PID=82266, 2.sh PID=82267),可以看出,两个脚本是分成两个进程运行的。

exec

exec

exec 方式运行的结果是,2.sh 执行完成后,不再回到 1.sh。运行顺序为 1-2。从pid值看,两者是在同一进程 PID=82287 中运行的。

source

source

source方式的结果是两者在同一进程里运行。该方式相当于把两个脚本先合并再运行。

总结

Command Explanation
fork 新开一个子 Shell 执行,子 Shell 可以从父 Shell 继承环境变量,但是子 Shell 中的环境变量不会带回给父 Shell。
exec 在同一个 Shell 内执行,但是父脚本中 exec 行之后的内容就不会再执行了
source 在同一个 Shell 中执行,在被调用的脚本中声明的变量和环境变量, 都可以在主脚本中进行获取和使用,相当于合并两个脚本在执行。

转载自

shell中局部变量及local命令

local一般用于局部变量声明,多在在函数内部使用。

(1)shell脚本中定义的变量是global的,其作用域从被定义的地方开始,到shell结束或被显示删除的地方为止。

(2)shell函数定义的变量默认是global的,其作用域从“函数被调用时执行变量定义的地方”开始,到shell结束或被显示删除处为止。函数定义的变量可以被显示定义成local的,其作用域局限于函数内。但请注意,函数的参数是local的。

(3)如果同名,Shell函数定义的local变量会屏蔽脚本定义的global变量。

1
2
3
4
5
6
7
8
9
#!/bin/bash  

function Hello()
{
local text="Hello World!!!" #局部变量
echo $text
}

Hello

转载自

CATALOG
  1. 1. 脚本文件开头
  2. 2. Shell 传递参数
    1. 2.1. 实例
    2. 2.2. 处理参数的特殊字符
    3. 2.3. 注意事项
  3. 3. Shell 赋值
  4. 4. Shell 脚本的if语句
    1. 4.1. if语法
    2. 4.2. 条件判断语句
    3. 4.3. 示例脚本
  5. 5. 在 Shell 脚本中调用另一个 Shell 脚本的三种方式
    1. 5.1. fork
    2. 5.2. exec
    3. 5.3. source
    4. 5.4. 例子
    5. 5.5. 总结
  6. 6. shell中局部变量及local命令