github email
初探Shell
Jul 4, 2018
5 minutes read

You got to put the past behind you before you can move on.

由于项目上的需要开始接触shell,其实shell早就有过接触,只不过这次接触了,深入的思考了。

0x00 为什么用shell?

原来学习一门语言的时候都会这样问自己?但是大多数的答案都是:项目上需要。没有思考的余地,也就没有深入的思考。

现在开始用到,也学习了python,ruby,swift,javascript后发现,shell有她自身更好的发挥余地。比如我们经常会使用的命令cd,hugo server,git add -A等。别小看这些命令,一个一个来,还是要输入一会的。程序员不就图个安逸,一个shell脚本搞定的事情,为什么要多来输入几次呢?在举个例子,如果终端只有一个,那么要看tomcat的日志,还要更新类,那么就会来回的路径下切换,如果不用文本记录下来,光敲路径就花费很长的时间,so,用个shell脚本就可以解决了。当然,这个例子也不能说明shell的强大之处,那么这个例子就是shell的重要用途了。在零部署的时候,需要安装一些组件,数据库,初始化数据库脚本,注册服务,启动服务,关机。整个流程可以说一气呵成,但是如果是人工的话,那么想想工作量也是吓人的,这还不包括部署多台服务器呢。

如果是安装系统,会非常喜欢ghost版本,安装就step by step。而shell就提供了这样功能。当然有人会说python也一样搞定。是的,不可否认,但是如果python自身也需要安装库呢?所以,shell独天得厚的优势在于系统自带的同时,还可以自动安装所需的命令。

结论就是:如果你需要将命令step by step执行,那么就选择shell吧。

0x01 Hello,World!

每个学习程序设计的开始都会是这个,也因为这个才从此入坑。

创建文件hello.sh,输入一下内容:

#!/bin/bash
echo 'Hello.World!'

在终端中使用sh hello.sh,将输出以下结果:

Hello,World!

恭喜你,入门了。

思考:

  1. 为什么第一行会有个#!/bin/bash
  2. echo 可以用单引号,也可以用双引号,区别是什么?
  3. 如果要让shell脚步可以直接运行,怎么做?
  4. 如何文件后缀是php,那么有该如何做呢?

0x02 变量

shell的变量命令基本和常用语言(pythoh/ruby/java/go)基本差不多,目前还没有遇到过坑。

那么如何定义一个变量呢?

someone="Lee"
some_two="http://10.10.10.222"

看似简单的定义变量,如果写多了程序,就会有个问题,如下:

someone = "Lee" #Eroor

为什么会有这个错误,因为写代码的时候都喜欢来个空格,这样看着顺眼些,但是在shell里是错误的。因为变量名和等号之间不能有空格。

变量名和等号之间不能有空格。

变量名和等号之间不能有空格。

变量名和等号之间不能有空格。

重要的事情说三遍,你要知道调试shell,就像java里System.out.println(“ok”)一样麻烦,更何况shell木有断点功能。目前我还不知道shell的ide,或者打断点,如果你知道,请告诉我,让我试试。

使用变量

$someone
$(some_two)

只读变量

readonly someone

删除变量

unset someone

0x03 字符串

单引号

someone='Lee'
  • 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;

  • 单引号字串中不能出现单引号(对单引号使用转义符后也不行)。

双引号

someone="Lee,$some_two"

拼接字符串

someone="World"
sometwo="hello,"$someone"!"
somethree="hello,${someone}!"
echo sometwo somethree

获取字符串长度

someone="hello"
echo ${#someone} #输出5

提取子字符串

someone="some one like you!"
echo ${someone:1:3} #输出ome,所以字符串的长度也是从0开始

查找字符串

someone="some one like you!"
echo `expr index "$someone" is` #output 2

0x04 Shell数组

shell支持数组,但是不支持多维数组。

定义数组

# one
someone=(val1 val2 val3)
#two
sometwo=(
v1
v2
v3
)
#three
arr[0]=key1
arr[1]=key2
arr[2]=key3

获取数组值

#one value
temp=${arr[0]}
#all
echo ${arr[@]} #@ mean all

数组长度

#count
length=${#arr[@]}
#or
length=${#arr[*]}
#one array length
length=${#arr[0]}

0x05 shell注释

shell里只能进行单行注释,已#开头的语句将会被注释掉。

# This is Comment!

# example shell script header 
#---------------------------------------------------
# author     :guoby
# date       :2018-07-05
# description: how to write comment in shell script
#---------------------------------------------------

如果没有多行注释不是太麻烦了?结论是有的。

那种好使用那种,我感觉我在mac下要测试一下,感觉two在我电脑上没有问题。

  • one
:<<EOF
comment!
comment!
comment!
EOF
  • two
:<<'
comment
comment
comment
'
  • three
:<<!
comment~~
comment~~
comment~~
!

0x06 shell参数传递

参数使用#n来进行获取

比如在终端执行:

sh job.sh testload.sh 20 
  • #0=job.sh

  • #1=testload.sh

  • #2=20

更多参数如下表:

参数 说明
$# 参数个数
$* 参数数组
$$ 脚本运行进程ID号
$! 后台运行的最后一个进程ID号
$@ 参数数组,使用双引号包含内容
$- 当前选项
$? 退出状态,0表示正常退出。

0x07 shell基本运算符

shell和其他语言一样,支持多运算符。

算术运算符

shell中使用expr来进行算术运算

add=`expr $((3+4))` #7
运算符 说明 举例
+ 加法 expr $a+$b
- 减法 expr $a-$b
* 乘法 expr $a\*$b
/ 除法 expr $a/$b
% 取余 expr $b%$a
= 赋值 a=$b
== 相等 [ $a==$b ]
!= 不相等 [ $a!=$b ]

关系运算符

a=1
b=2
运算符 说明 举例
-eq 检查两个数是否相等,相等返回true [ $a -eq $b ] 返回false
-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

布尔运算符

运算符 说明 举例
! 非运算,表达式为 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

逻辑运算符

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

字符串运算符

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

文件测试运算符

文件运算符用于检测Unix文件的各种属性。

file="~/test.sh"
运算符 说明 举例
-b file 检测文件是否是块设备文件,如果是,则返回 true [ -b $file ] 返回 false
-c file 检测文件是否是字符设备文件,如果是,则返回 true [ -c $file ] 返回 false
-d file 检测文件是否是目录,如果是,则返回 true [ -d $file ] 返回 false
-f file 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true [ -f $file ] 返回 true
-g file 检测文件是否设置了 SGID 位,如果是,则返回 true [ -g $file ] 返回 false
-k file 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true [ -k $file ] 返回 false
-p file 检测文件是否是有名管道,如果是,则返回 true [ -p $file ] 返回 false
-u file 检测文件是否设置了 SUID 位,如果是,则返回 true [ -u $file ] 返回 false
-r file 检测文件是否可读,如果是,则返回 true [ -r $file ] 返回 true
-w file 检测文件是否可写,如果是,则返回 true [ -w $file ] 返回 true
-x file 检测文件是否可执行,如果是,则返回 true [ -x $file ] 返回 true
-s file 检测文件是否为空(文件大小是否大于0),不为空返回 true [ -s $file ] 返回 true
-e file 检测文件(包括目录)是否存在,如果是,则返回 true [ -e $file ] 返回 true

0x08 shell echo command

显示普通字符串

echo "Just fun!"

显示转义字符

echo "\"This is demo!\""
#output "This is demo!"

显示变量

yourname="Lee"
echo "you name is $yourname"
#output you name is Lee

显示换行

echo -e "Now,Let's talk about you life!\n" # -e 开启转义
echo "It sample."
#output
Now,Let's talk about you life!

It sample.

显示不换行

echo -e "you work is \c"
echo "good!"
#output you work is good!

将结果输出到文件

echo "This comment in file." > log.txt

输出变量信息

echo `$yourname\"`
#output $yourname\"

显示命令执行结果

echo `whoami`
#output root

0x09 shell printf command

printf命令模仿C程序里的printf()函数。

printf "%-10s %-8s %-4s\n" 姓名 性别 体重(kg)
printf "%-10s %-8s %-4.2f\n" 张三 男 54.333
printf "%-10s %-8s %-4.2f\n" 李四 男 66.234
printf "%-10s %-8s %-4.2f\n" 杨玉环 女 48.872

输出

姓名   性别     体重
张三   男       54.33
李四   男       66.23
杨玉环 女       48.88

转义序列

序列 说明
\a 警告字符,通常为ASCII的BEL字符
\b 后退
\c 抑制(不显示)输出结果中任何结尾的换行字符
\f 换页(formfeed)
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\ 反斜杠字符
\ddd 表示1到3位数八进制值的字符。仅在格式字符串中有效
\0ddd 表示1到3位的八进制值字符

0x10 shell test command

shell中的 test 命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试。

数值测试

参数 说明
-eq 等于则为真
-ne 不等则为真
-gt 大于则为真
-ge 大于等于则为真
-lt 小于则为真
-le 小于等于则为真
zs_age=28
ls_age=29

if test $[zs_age] -eq $[ls_age]
then 
	echo 'zs age equal ls'
else
	echo 'zs age no equal ls'
fi 

字符串测试

参数 说明
= 等于则为真
!= 不等于则为真
-z字符串 字符串的长度为零则为真
-n字符串 字符串长度不为零则为真
zs_name="张三"
ls_name="李四"

if test $zs_name = $ls_name
then 
	echo "两个名字一致"
else
	echo "两个名字不一致"
fi 

文件测试

参数 说明
-e文件名 如果文件存在则为真
-r文件名 如果文件存在且可读则为真
-w文件名 如果文件存在且可写则为真
-x文件名 如果文件存在且可执行则为真
-s文件名 如果文件存在且至少有一个字符则为真
-d文件名 如果文件存在且为目录则为真
-f文件名 如果文件存在且为普通文件则为真
-c文件名 如果文件存在且为字符型特殊文件则为真
-b文件名 如果文件存在且为块特殊文件则为真

0x11 shell 控制流量

if else

if condition
then 
	command1
	command2
	...
	command3
else
	command5
if
if condition
then
	command1
elif 
	command2
then
	command3
else
	commandn
fi

for 循环

for var in var1 var2 ... varn
do
	cmd1
	cmd2
	cmd3
	cmdn
done

while 语句

while condition
do
	cmd
done

until 循环

until condition
do 
	cmd 
done

case

case 值 in 
条件1)
	cmd1
	;;
条件2)
	cmd2
	;;
esac

break continue

break 跳出所有循环

continue 跳出当前循环,继续下一次循环

esac

case的语法和C family语言差别很大,它需要一个esac(就是case反过来)作为结束标记,每个case分支用右圆括号,用两个分号表示break。

0x12 shell function

inux shell 可以用户定义函数。

函数定义

[ function ] funname [()]

{

    action;

    [return int;]

}

说明:

  1. 可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。

  2. 参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255)。

行数参数

在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数,依次类推。

0x13 输入输出重定向

重定向命令列表如下:

命令 说明
command > file 将输出重定向到 file
command < file 将输入重定向到 file
command » file 将输出以追加的方式重定向到 file
n > file 将文件描述符为 n 的文件重定向到 file
n » file 将文件描述符为 n 的文件以追加的方式重定向到 file
n >& m 将输出文件 m 和 n 合并
n <& m 将输入文件 m 和 n 合并
« tag 将开始标记 tag 和结束标记 tag 之间的内容作为输入

需要注意的是文件描述符 0 通常是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。

/dev/null 文件

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

$ command > /dev/null

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

如果希望屏蔽 stdout 和 stderr,可以这样写:

$ command > /dev/null 2>&1

0x14 引用文件

和其他语言一样,Shell 也可以包含外部脚本。这样可以很方便的封装一些公用的代码作为一个独立的文件。

Shell 文件包含的语法格式如下:

. filename   # 注意点号(.)和文件名中间有一空格

或

source filename

实例

#sh_test1.sh
name="guoby"
#sh_test2.sh

#使用 . 号来引用sh_test1.sh 文件
. ./sh_test2.sh
echo "you name is $name"

结论

shell适用于在命令集合的情况下完成任务,并不适合于复杂的程序任务。

最实用的实用场景:发布部署应用。


Back to posts