嘛,作为习惯使用Mac作为开发系统的开发者,Shell算是必须学习的内容之一。CI的脚本之类的也需要用Shell来写,总之学习一下没有坏处。本文主要以基础的Bourne Shell为学习对象,记录学习过程中的一些知识要点,和其他笔记一样,基本上是写给自己看的,不喜欢的不要拍砖。
1.关于 #!
'#'表示注释,而'#!'则告诉系统当前文件应该用什么来执行。
例如:
#!/bin/sh
echo Hello World
/bin/sh是Bourne shell在Unix下的标准路径,如果是Linux下,它则是一个指向bash(最新版是dash)的软连接。
2.关于chmod
对用户创建的文件来说,默认只有读写权限,没有执行权限。为了让脚本可以被执行,就需要通过chmod命令修改权限。
r
表示读权限 用数字表示为4
w
表示写权限 用数字表示为2
x
表示执行权限 用数字表示为1
-
表示无权限 用数字表示为0
chmod 777 test.sh
3.变量赋值
变量在赋值的时候没有空格,否则系统会以为这是一条执行命令语句,例如:
#!/bin/sh
MY_MESSAGE="Hello World"
echo $MY_MESSAGE
如果=左右有空格的话,系统还以为MY_MESSAGE是一个命令,而=和“Hello world”是命令的两个参数。
如果变量没有被赋值,那么变量将被视为一个空字符串。
4.使用read命令
read命令可以从标准输入中读取一行并将其赋值给你提供的变量。
#!/bin/sh
echo "What is your name?"
read MY_NAME
echo "Your name is $MY_NAME"
注意到read命令能够自动给你的输入加一对双引号,这意味着你可以安全的使用空格。
What is your name?
Grand Kindom
Your name is Grand Kindom
5.变量的作用域
一般来说,变量的作用域只限于当前的shell环境,也就是#!bin/sh
之下的环境中。
例如,我们有如下的shell脚本:
#!/bin/sh
echo "MY_VAR is $MY_VAR"
MY_VAR="Hello"
echo "MY_VAR is $MY_VAR"
现在我们执行脚本:
$ MY_VAR=hello
$ ./test.sh
MY_VAR is
MY_VAR is Hello
可以发现,我们在交互式shell(interactive shell)
下赋值的变量,对新生成的用来运行脚本的子shell环境没有任何影响。
为了能让子shell可以使用我们的变量,需要使用export
命令允许子shell继承变量。
$ MY_VAR=World
$ export MY_VAR
$ ./test.sh
MY_VAR is World
MY_VAR is Hello
脚本中的第三行,我们更改了MY_VAR变量的值,我们尝试在交互式shell下输出:
$ echo $MY_VAR
World
发现值并没有发生任何变化,也就是说子shell对父shell的变量没有影响。
为了做到这点,我们需要将脚本在当前的交互式shell中运行,而不是新生成一个shell来运行。这一过程叫做source
。通过.
命令,我们可以source一个脚本:
$ . ./test.sh
MY_VAR is World
MY_VAR is Hello
$ echo $MY_VAR
Hello
MY_VAR变量被成功改变了!这也是.profile
和.bashrc
的工作原理。
6.变量的边界
考虑以下脚本
#!/bin/sh
echo "Please tell your name"
read MY_NAME
echo "Hello $MY_NAME"
echo "Now I will create a file called $MY_NAME_file"
touch "$MY_NAME_file"
文件能够创建成功吗?不行。因为shell不知道变量的起始位置,它还以为有一个变量的名字叫做'MY_NAME_file',而事实上并没有这样的一个变量,因此文件创建失败了。
使用花括号可以界定变量的边界,他告诉shell:花括号内的是一个变量,需要单独对待。
echo "Please tell your name"
read MY_NAME
echo "Hello $MY_NAME"
echo "Now I will create a file called ${MY_NAME}_file"
touch "${MY_NAME}_file"
再次运行上述修改后的脚本,我们会发现文件被成功创建。
为什么touch命令的参数要用双引号圈起来呢?这是为了防止你输入的名字有空格。比如说你输入的名字是Grand Kindom
,那么最后的touch命令就会变成touch Grand Kingdom_file
。结果会生成两个文件,一个叫做Grand
,另一个叫做Kindom_file
。
7.循环
1)for循环
for i in hello 1 * 2 world
do
echo "i is set to $i in the loop now"
done
2)while循环
INPUT_STRING=hello
while [ "$INPUT_STRING" != "bye" ]
do
echo "Please type something in (bye to quit)"
read INPUT_STRING
echo "You typed: $INPUT_STRING"
done
当while后跟着一个:
时,条件表达式永远为true
,这在某些情况下很有用。
while :
do
echo "Please type something int (^C to quit)"
read INPUT_STRING
echo "You typed: $INPUT_STRING"
done
另一个比较常见的使用方式是while read f
循环
while read f
do
case $f in
hello) echo English ;;
howdy) echo American ;;
gday) echo Australian ;;
bonjour) echo French ;;
"guten tag") echo German ;;
*) echo Unknown Language: $f
;;
esac
done < myfile
8.测试
在shell中,[
代表测试,它本身是一个程序,就像ls
、chmod
一样,而不是一个符号。所以[
后面必须跟空格,否则shell会把[
和后面的字符拼在一起解释,导致问题。在Unix系统中,[
是一个指向test
程序的软连接,也就是说:
if [$foo = "bar" ]
会被解释为if test$foo = "bar" ]
,这也就是问题的所在了。可以看到,在shell中,使用=
比较字符串,而对于整数则使用-eq
。
1) if
if [ ... ]
then
...
else
...
fi
注意then必须另起一行。也可以加上;
使其和if在同一行。在shell中;
表示后面的语句是另一行语句,不过出于某种目的(比如说节约空间)所以写在同一行。与其对应的符号是\
,表示下一行的语句应该和本行语句视为同一行。下面的第二个shell运用了逻辑运算符的短路性质。
if [ ... ]; then
...
fi
[ "$X" -nt "/etc/passwd" ] && \
echo "X is a file which is newer than /etc/passwd"
也可以使用elif
if [ something ]; then
echo "Something"
elif [ something_else ]; then
echo "Something else"
else
echo "None of the above"
fi
9.预设变量
1) $0 $1 ... $9
和 $#
-
$0
表示当前程序(脚本)的名称 -
$1 .. $9
表示调用脚本时候传入的前9个参数 - 变量
$@
表示所有参数$*
也有类似的作用但是它不保留空格和引号(通常情况下避免使用$*
) -
$#
表示脚本调用时候传入的参数个数
#!/bin/sh
echo "I was called with $# parameters"
echo "My name is $0"
echo "My first parameter is $1"
echo "My second parameter is $2"
echo "All parameters are $@"
$ ./var3.sh hello world earth
I was called with 3 parameters
My name is ./var3.sh
My first parameter is hello
My second parameter is world
All parameters are hello world earth
2) $?
这个变量包含上一个命令的退出值。在shell中,返回值0表示正常退出,所以可以通过test来判断上一个命令执行的时候有没有执行成功,增加程序的鲁棒性。
#!/bin/sh
/usr/local/bin/my-command
if [ "$?" -ne "0" ]; then
echo "Sorry, we had a problem there!"
fi
3)$$
和$!
-
$$
变量表示当前的进程标识符,如果你的脚本允许存在多个实例同时运行的话,在创建文件的时候就可以在文件名上带上这个进程标识符避免冲突:/temp/my_file.$$
。 -
$!
变量表示最后运行的后台进程标识符
10.花括号的高级用法
花括号除了用来保证变量的边界外,还可以用来处理字符串undefined
或者为null
的情况(这两者在shell里基本没有区别),为其添加一个默认值,方法是在变量名后面加上:-
#!/bin/sh
echo "What is your name [ `whoami` ] \c"
read MY_NAME
echo "Hello ${MY_NAME:-`whoami`}"
上述脚本中的\c
告诉echo命令不要换行。反引号中的内容whoami
被视为一个命令,命令在执行之后的标准输出会被替换为反引号所在的内容。而whoami
命令输出的是当前登录的用户名称,所以上述命令的作用是让用户键入自己的名称,并且会显示当前登录的用户名称作为默认值,如果用户直接点了回车没有输入内容的话,则会输出用户的登录名。
$ ./test.sh
What is your name [ melot ] abc
Hello abc