shell是Linux操作系统的用户接口,我们经常需要编写脚本让操作系统自动执行一系列指令的需求,本文将简单介绍开发shell脚本的所需的语言特性。

shell脚本是指令序列,其指令可以直接在终端中执行。同样地,终端中的指令也可以写到脚本中。

脚本文件通常以.sh作为后缀名,第一行以#!开头指定执行脚本的程序:

#!/usr/bin/bash

#!开头的第一行被称为Hashbang或Shebang。 而#是shell脚本中的行注释符。

通常有三种执行脚本的方式:

  • sh start.sh: 在终端中创建一个sh子进程执行脚本, 执行者需要拥有脚本的读权限。

    该方式实际上是将脚本路径作为参数传递给了sh命令。
  • source start.sh: 在终端中执行脚本,相当于将脚本中的指令逐条复制到终端执行。

    脚本中局部变量将保留在终端环境变量中, 脚本的pid和工作目录等环境也与终端一致。
  • ./start.sh: 根据HashBang指定的程序,在子进程中执行脚本。

sh命令有一些有用的选项帮助我们开发和调试脚本:

  • sh -n start.sh:对脚本进行语法检查, 不实际执行脚本
  • sh -x start.sh: 把将要执行的命令输出到stderr便于进行调试

变量

shell中变量是弱类型的, 变量名只能包含字母、数字或下划线"_",首字符只能为字母。

shell主要面向文本处理而非数据计算,因此变量默认类型为字符串型。

A=abc
echo $A

变量在使用前无需声明,在为变量赋值时=左右不能添加空格。

A = abc会被shell解释为执行指令A,参数为=abc

$为变量标志符, echo $A指令将显示变量A的内容abc, 为了明确指定变量名也可以写作${A}

A=a
AB=ab
echo ${A}B

$(cmd)可以把命令的输出作为返回值, 如:

PWD=$(pwd)

变量$PWD存储了当前的工作目录路径。

在shell中可以直接书写字符串,但仍建议用单引号或双引号标识字符串。

在单引号标识的字符串中$不被作为变量标识符, 而双引号则会将$替换为变量内容。

A="abc"
echo '$A' # $A
echo "$A" # abc

字符串拼接不需要任何运算符,只需要将它们写在一起即可:

A="abc"
B="123"
echo "$A+$B" # abc+123
echo "$A$B" # abc123
echo "$Adef" # abcdef

全局变量

变量按照作用域可以分为局部变量和全局变量,上文示例中定义的变量都是局部变量, 作用域仅限执行脚本的进程。

子进程可以继承父进程的全局变量,export指令用于定义全局变量:

export A=abc

整型变量

shell仅支持整型计算, declare命令可以声明整型变量,let指令用于算术运算:

declare -i a=1
let a=a+1
echo $a # 2
let a+=1
echo $a # 3

let指令支持算术运算符包括:

  • +:加法
  • -: 减法
  • *: 乘法
  • /: 除法
  • **: 乘方
  • %: 取余

let指令也支持算术运算符对应的算术赋值运算符,如+=

数组

bash中可以使用圆括号定义数组,元素之间用空格分割,数组下标从1开始:

arr=(1 'a' "abc")
echo ${arr[1]}

也可以直接使用下标定义数组:

arr2[1]=1
arr2[2]=2

该方法同样可以用于修改已存在的数组。

特殊变量

shell中预定义了一些特殊变量,通过这些变量可以获得环境信息:

  • $$: 执行脚本的进程ID(pid)
  • $?: 上一条命令的返回值
  • $!: 上一条后台指令的执行进程的ID

上述变量在交互式终端中同样有效。

还有一些变量可以获得执行脚本时传入的参数:

  • $0: 脚本的文件名
  • $1~$n: 传给脚本的第n个参数
  • $#: 传入参数的个数
  • $@: 参数列表
  • $*: 单个字符串形式的参数列表

流程控制

if

declare -i a=90
if [ $a -gt 80 ]; then
echo "A"
elif [ $a -lt 60 ]; then
echo "C"
else
echo "D"
fi

结束标志fi即是if反写, 我们还将在其它地方遇到bash的这种命名风格。

注意,[]旁边的空格不可省略。

-lt, -gt用于进行整型的大小比较:

  • -eq: 等于(equal)
  • -ne: 不等于(not equal)
  • -gt: 大于(greater)
  • -ge: 大于等于(greater-equal)
  • -lt: 小于(less)
  • -le: 小于等于(less-equal)

进行复合逻辑判断也很简单:

if [ $a -gt 60 -a ( ! $a -gt 90 -o $a eq 91 ) ]; then
echo "make no sense"
fi
  • !: 非
  • -a: 且and
  • -o: 或-o

逻辑运算遵循短路计算原则。

<, >等运算符在[]中只能用于字符串的比较, 而在[[]]<, >可以用于整型和字符串的大小比较, 也可以使用&&||来书写逻辑表达式。

if的条件判断不一定使用[][[]]表达式,它可以是任何一个命令。命令的返回值为0则if判断为真, 非0判断为假。

[][[]]转义表达式也可以像普通指令一样执行,判断为真则返回0,假则返回非0值。

[ 2 -gt 1 -a 3 -lt 4 ] && echo 'ok'

除此之外,if还可以进行更多种类的条件判断:

判断字符串相等

if [ ${NAME} = 'tmp' ]; then
echo "name is tmp"
fi

判断文件是否存在

if [ -e tmp ]; then
echo "tmp exists"
fi

判断tmp是否存在,tmp可以是目录或文件。

判断是否为普通文件

if [ -f tmp ]; then
echo "file tmp exists"
fi

判断tmp是否为文件,tmp不能是目录。

判断是否为目录

if [ -d tmp ]; then
echo "directory tmp exists"
fi

判断是否具有执行权限

if [ -x tmp ]; then
echo "tmp is executable"
fi

不判断文件是否可执行,只判断是否拥有x权限。 因此,tmp为有x权限的目录时也会判断为真。

类似的还有,-w判断是否拥有写入权限, -r判断是否拥有读取权限。

判断是否为空文件

if [ -s tmp ]; then
echo "file is not empty"
fi

case

case类似于其它语言中的switch语句:

case ${NAME} in
'a')
echo "name is a"
;;
'b')
echo "name is b"
;;
*)
echo "other names"
;;
esac

从第一个匹配的标签开始执行, 两个标签之间必须有;;*)是其它标签都不匹配时的默认标签。

for

for循环可以遍历一个序列:

for i in $(seq 1 10); do
echo $i
done
# echo: 1 2 3 ... 10

一些命令的输出也可以作为序列:

for i in $(ls); do
echo $i
done

遍历所有参数:

for arg in "$@"; do
echo $arg
done

另一种形式的for循环:

for (( i=0; i<100; i++)); do
echo $i
done

while

declare -i i=0
while [ $i -lt 10 ]; do
echo $i
i=$i+1
done

while(true)这样的死循环也很容易:

declare -i i=0
while ; do
echo $i
[ ! $i -lt 10 ] && break
i=$i+1
done

函数

shell提供了定义函数的功能, 函数就像是脚本中的子脚本:

range() {
for (( i=0; i<${1}; i++)); do
echo $i
done
return ${1}
} range 100

函数同样使用位置参数$1~$n来访问参数,$0为函数的名称, $@, $#等变量的含义不变。

进程间通信

管道

管道用于将上一条指令的输出作为下一条指令的输入:

ls | grep ".zip"

xargs

有一些指令不支持使用管道传递参数,因此需要xargs命令

find ~ | xargs ls

xargs会以空格为分隔符将输入分隔为参数,然后将参数传给ls。

重定向

重定向用于将命令的输入输出从标准流重定向到文件。标准流包括:

  • stdin: 标准输入流,文件描述符为0
  • stdout: 标准输出流,文件描述符1
  • stderr: 标准错误流,文件描述符2

输出到文件,覆盖原有内容:

echo "hello" > 1.txt

输出到文件, 追加到文件尾:

echo "hello" >> 1.txt

从文件输入:

wc -l < 1.txt

重定向标准错误输出流:

cmd 2> 2.txt

将标准错误输出追加到文件:

cmd 2>> 2.txt

将标准错误和标准输出一同重定向到文件:

cmd > 1.log 2>&1

2>&1是将stderr重定向到stdout。

后台执行

shell可以执行一行指令后立即返回, 返回后可以通过$?变量获得执行进程的ID:

$ sleep 10 &
[1] 79403
$ echo $!
79403

Shell与脚本的更多相关文章

  1. shell及脚本4——shell script

    一.格式 1.1 开头 必须以 "# !/bin/bash"  开头,告诉系统这是一个bash shell脚本.注意#与!中间有空格. 二.语法 2.1 数值运算 可以用decla ...

  2. 【Telnet】使用Telnet协议连接到远程Shell执行脚本

    介绍 本文介绍如何通过Telnet协议连接到远程Shell,执行脚本,并获取执行结果: 相关文章: <[Jsch]使用SSH协议连接到远程Shell执行脚本>http://www.cnbl ...

  3. shell自动计算脚本

    shell自动计算脚本 #!/bin/bash echo $(($)) [root@bogon ~]# sh b.sh 123+123246 let用户声明这个操作是要计算,后者的效率更高 (expr ...

  4. Shell菜单脚本

    今天在这儿给大家分享一个我简单编写的Shell菜单脚本,傻瓜式的人机交互,人人都可以操作linux. #!/bin/sh #Shell菜单演示 function menu () { cat <& ...

  5. shell常见脚本30例

    shell常见脚本30例 author:headsen chen  2017-10-19  10:12:12 本文原素材出自网上,特此申明.有些地方加入我自己的改动 常见的30例shell脚本 1.用 ...

  6. shell常用脚本

    shell常用脚本 author:headsen chen  2017-10-17 15:36:17 个人原创,转载请注明,否则依法追究法律责任 1,vim  name.grep.sh 2,cat   ...

  7. 一篇关于Maven项目的jar包Shell启动脚本

    使用Maven作为项目jar包依赖的管理,常常会遇到命令行启动,笔者也是哥菜鸟,在做微服务,以及服务器端开发的过程中,常常会遇到项目的启动需要使用main方法,笔者潜心的研究了很多博客,发现大多写的都 ...

  8. Linux shell编写脚本部署pxe网络装机

    Linux shell编写脚本部署pxe网络装机 人工安装配置,Linux PXE无人值守网络装机  https://www.cnblogs.com/yuzly/p/10582254.html 脚本实 ...

  9. 使用shell解析脚本依赖关系,并自动补数

    将脚本依赖关系放到表中 使用shell解析脚本依赖关系,递归的计算各个脚本. #!/bin/bash # dm 补数 basepath=$(cd ``; pwd) cd $basepath sourc ...

  10. shell监控脚本

    序言: 前几天一好友问我服务器监控怎么做?你们公司的监控是怎么做的?有什么开源的监控软件推荐?常见的开源的监控软件当然首先推荐ZABBIX,分布式够强大,而且很多公司都在用,我问他具体什么需求,能监控 ...

随机推荐

  1. hdu_1012(水题。。。不能再水)

    #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using ...

  2. js闭包的真正理解

    <高级程序设计>上,这样说:当在函数内部定义了其他函数时候,就创建了闭包.闭包有权访问包含函数内部的所有变量. 这个说的太晦涩了,而且我觉得很容易理解错,闭包就是一个嵌套函数嘛?但是我觉得 ...

  3. Proxy 那点事儿

    ---恢复内容开始--- 尊重原创:https://my.oschina.net/huangyong/blog/159788 Proxy,也就是"代理"了.意思就是,你不用去做,别 ...

  4. 详解 Vue 2.4.0 带来的 4 个重大变化

    在这篇文章中,我将跟大家分享4个有突破性新特性. 服务端渲染异步组件 包裹组件内实现属性继承 异步组件支持webpack3 组件渲染后可保留HTML注释 1.服务端渲染异步组件 在vue2.4.0以前 ...

  5. grunt 插件开发注意事项

    grunt的插件机制 task.loadNpmTasks = function(name) { var root = path.resolve('node_modules'); var tasksdi ...

  6. PHP性能分析工具xhprof的安装使用与注意事项

    前言 xhprof由facebook开源出来的一个PHP性能监控工具,占用资源很少,甚至能够在生产环境中进行部署. 它可以结合graphviz使用,能够以图片的形式很直观的展示代码执行耗时. 下面主要 ...

  7. 第一章 用HTML5中的结构元素构建网站

    1.当一个容器需要直接定义样式或通过脚本定义行为时,推荐使用div元素而非section元素. 2.section是需要标题的,而nav或aside没有标题也是可以的. 3.html5轮廓工具 htt ...

  8. VC++ 6.0 中使用 MSComm.ocx

    很多人喜欢单独安装VC++6.0,而不是完整安装VS,这样占用空间比较少,启动也快.但是要使用某些ActiveX控件的时候却会出现许可证问题(requires a design-time licenc ...

  9. Android开发之漫漫长途 XIII——Fragment最佳实践

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  10. elasticsearch安装ik分词器

    一.概要: 1.es默认的分词器对中文支持不好,会分割成一个个的汉字.ik分词器对中文的支持要好一些,主要由两种模式:ik_smart和ik_max_word 2.环境 操作系统:centos es版 ...