转载:http://mp.weixin.qq.com/s?__biz=MzA3MTIxNzkyNg==&mid=204081791&idx=1&sn=27bb1d827e0f8582596f090471a5c098&scene=5#rd

简单shell脚本

!/bin/bash

这一行表明,不管用户选择的是那种交互式shell,该脚本需要使用bash shell来运行。由于每种shell的语法大不相同,所以这句非常重要。

简单实例

下面是一个非常简单的shell脚本。它只是运行了几条简单的命令

1
2
3
4
#!/bin/bash
echo "hello, $USER. I wish to list some files of yours"
echo "listing files in the current directory, $PWD"
ls # 列出当前目录所有文件

首先,请注意第四行。在bash脚本中,跟在#符号之后的内容都被认为是注释(除了第一行)。Shell会忽略注释。这样有助于用户阅读理解脚本。 ?$USER和 $PWD都是变量。它们是bash脚本自定义的标准变量,无需在脚本中定义即可使用。请注意,在双引号中引用的变量会被展开(expanded)。“expanded”是一个非常合适的形容词:基本上,当shell执行命令并遇到$USER变量时,会将其替换为该变量对应的值。

变量

任何编程语言都会用到变量。你可以使用下面的语句来定义一个变量:

1
X="hello"

并按下面的格式来引用这个变量:

$X

更具体的说,$X表示变量X的值。关于语义方面有如下几点需要注意:

  • 等于号两边不可以有空格!例如,下面的变量声明是错误的 :

1
X = hello
  • 在我所展示的例子中,引号并不都是必须的。只有当变量值包含空格时才需要加上引号。例如:

1
2
X = hello world # 错误
X = "hello world" # 正确

这是由于shell将每一行命令视为命令及其参数的集合,以空格分隔。 foo=bar就被视为一条命令。foo = bar 的问题就在于shell将空格分开的foo视为命令。同样,X=hello world的问题就在于shell将X=hello视为一条完整的命令,而”world”则被彻底无视(因为赋值命令不需其他参数)。

单引号 VS 双引号

基本上来说,变量名会在双引号中展开,单引号中则不会。如果你不需要引用变量值,那么使用单引号可以很直观的输出你期望的结果。 An example 示例

1
2
3
4
#!/bin/bash
echo -n '$USER=' # -n选项表示阻止echo换行
echo "$USER"
echo "\$USER=$USER" # 该命令等价于上面的两行命令

输出如下(假设你的用户名为elflord)) $USER=elflord $USER=elflord

1
2
3
$USER=elflord
 
$USER=elflord

从例子中可以看出,在双引号中使用转义字符也是一种解决方案。虽然双引号的使用更灵活,但是其结果不可预见。如果要在单引号和双引号之间做出选择,最好选择单引号。

使用引号封装变量

有时候,使用双引号来保护变量名是个很好的点子。如果你的变量值存在空格或者变量值为空字符串,这点就显得尤其重要。看下面这个例子:

1
2
3
4
5
#!/bin/bash
X=""
if [ -n $X ]; then # -n 用来检查变量是否非空
echo "the variable X is not the empty string"
fi

运行这个脚本,输出如下:

the variable X is not the empty string

为何?这是因为shell将$X展开为空字符串,表达式[-n]返回真值(因为改表达式没有提供参数)。再看这个脚本:

1
2
3
4
5
#!/bin/bash
X=""
if [ -n "$X" ]; then # -n 用来检查变量是否非空
echo "the variable X is not the empty string"
fi

在这个例子中,表达式展开为[ -n ""],由于引号中内容为空,因此该表达式返回false值。

在执行时展开变量

为了证实shell就像我上面说的那样直接展开变量,请看下面的例子:

1
2
3
4
5
#!/bin/bash
LS="ls"
LS_FLAGS="-al"
 
$LS $LS_FLAGS $HOME

乍一看可能有点不好理解。其实最后一行就是执行这样一条命令:

Ls -al /home/elflord

(假设当前用户home目录为/home/elflord)。这就说明了shell仅仅只是将变量替换为对应的值再执行命令而已。

使用大括号保护变量

这里有一个潜在的问题。假设你想打印变量X的值,并在值后面紧跟着打印”abc”。那么问题来了:你该怎么做呢? 先试一试:

1
2
3
#!/bin/bash
X=ABC
echo "$Xabc"

这个脚本没有任何输出。究竟哪里出了问题?这是由于shell以为我们想要打印变量Xabc的值,实际上却没有这个变量。为了解决这种问题可以用大括号将变量名包围起来,从而避免其他字符的影响。下面这个脚本可以正常工作:

!/bin/bashX=ABCecho “${X}abc”

1
2
3
#!/bin/bash
X=ABC
echo "${X}abc"

条件语句, if/then/elif

在某些情况下,我们需要做条件判断。比如判断字符串长度是否为0?判断文件foo是否存在?它是一个链接文件还是实际文件?首先,我们需要if命令来执行检查。语法如下:

1
2
3
4
5
6
if condition
then
statement1
statement2
..........
fi

当指定条件不满足时,可以通过else来指定其他执行动作。

1
2
3
4
5
6
7
8
if condition
then
statement1
statement2
..........
else
statement3
fi

当if条件不满足时,可以添加多个elif来检查其他条件是否满足。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if condition1
then
statement1
statement2
..........
elif condition2
then
statement3
statement4
........
elif condition3
then
statement5
statement6
........
 
fi

当相关条件满足时,shell会执行在相应的if/elif与下个elif或fi之间的语句。事实上,判断条件可以是任意命令,当且只当命令返回并且退出状态为0时,才会执行该条件块中的语句(换句话说,就是当命令成功返回时)。不过在本文的学习中,我们只会关注“test”或“[]”形式的条件判断。

Test命令与操作符

条件判断中的命令几乎都是test命令。test根据测试条件通过或失败来返回true或false(更准确的说是返回0或非0值)。如下所示:

1
test operand1 operator operand2

对某些测试来说,只需要一个操作数(operand2)通常是下面这种情况的简写:

1
[ operand1 operator operand2 ]

为了让我们的讨论更接地气一点,给出下面一些例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash
X=3
Y=4
empty_string=""
if [ $X -lt $Y ] # is $X less than $Y ?
then
echo "$X=${X}, which is smaller than $Y=${Y}"
fi
 
if [ -n "$empty_string" ]; then
echo "empty string is non_empty"
fi
 
if [ -e "${HOME}/.fvwmrc" ]; then # test to see if ~/.fvwmrc exists
echo "you have a .fvwmrc file"
if [ -L "${HOME}/.fvwmrc" ]; then # is it a symlink ?
echo "it is a symbolic link"
elif [ -f "${HOME}/.fvwmrc" ]; then # is it a regular file ?
echo "it is a regular file"
fi
else
echo "you have no .fvwmrc file"
fi

需要注意的细节

Test命令的格式为“操作数< 空格 >操作符< 空格 >操作数”或者“操作符< 空格 >操作数”,这里特别说明必须要有这些空格,因为shell将没有空格的第一串字符视为一个操作符(以-开头)或者操作数。比如下面这个:

if [ 1=2 ]; then echo “hello”fi

它会打印出hello,这明显与预期结果是不一致的(因为shell只看到操作数1=2,没看到操作符)。

还有一种隐藏陷阱是未加引号的变量。像我们之前例子说的-n测试时变量须加引号的情形。其实,不管在什么情况下,加上引号总是没有坏处的,还有可能规避一些很奇葩的错误。因为有时候不加引号的变量扩展开的测试结果会让人非常困惑。例如:

1
2
3
4
5
6
#!/bin/bash
X="-n"
Y=""
if [ $X = $Y ] ; then
echo "X=Y"
fi

这个脚本打印出来的结果是错误的,因为shell将判断展开为 [ -n = ],但是”=”的长度不为0,所以条件判断通过从而导致输出结果为“X=Y”。

Test操作符简介

下图是test操作符的快速查询列表。当然这个列表并不全面,但记下这些就足够平常使用了(如果还需要了解其他操作符,可以查看man手册)。

operator produces true if… number of operands
-n operand non zero length 1
-z operand has zero length 1
-d there exists a directory whose name is operand 1
-f there exists a file whose name is operand 1
-eq the operands are integers and they are equal 2
-neq the opposite of -eq 2
= the operands are equal (as strings) 2
!= opposite of = 2
-lt operand1 is strictly less than operand2 (both operands should be integers) 2
-gt operand1 is strictly greater than operand2 (both operands should be integers) 2
-ge operand1 is greater than or equal to operand2 (both operands should be integers) 2
-le operand1 is less than or equal to operand2 (both operands should be integers) 2

循环

循环结构允许我们执行重复的步骤或者在若干个不同条目上执行相同的程序。Bash中有下面两种循环

  • for 循环

  • while 循环

For 循环

直接来个例子,来直观地感受for循环的语法。

1
2
3
4
5
#!/bin/bash
for X in red green blue
do
echo $X
done

For循环会遍历空格分开的条目。注意,如果某一项含有空格,必须要用引号引起来,例子如下:

1
2
3
4
5
6
7
8
#!/bin/bash
colour1="red"
colour2="light blue"
colour3="dark green"
for X in "$colour1" $colour2" $colour3"
do
echo $X
done

如果我们漏掉for循环中的引号,你能猜想出会发生什么吗?这个例子说明,除非你确认变量中不会包含空格,否则最好都用引号将变量保护起来。

在for循环中使用通配符

如果shell解析字符串时遇到*号,会将它展开为所有匹配的文件名。当且仅当目标文件与号展开后的字符串一致才会匹配成功。例如,单独的*号展开为当前目录的所有文件,中间以空格分开(包含隐藏文件)。

所以:

echo *

列出当前目录下的所有文件和目录。

echo *.jpg

列出所有的jpeg图片格式的文件。

echo ${HOME}/public_html/*.jpg

列出home目录中public_html目录下的所有jpeg文件。

正是由于这种特性,使得我们可以很方便的来操作目录和文件,尤其是和for循环结合使用时,更是便利。例子如下:

1
2
3
4
5
#!/bin/bash
for X in *.html
do
grep -L '<UL>' "$X"
done

打印出当前目录下所有不包含<UL>字段的html文件。

While 循环

当给定条件为真值时,while循环会重复执行。例如:

1
2
3
4
5
6
7
#!/bin/bash
X=0
while [ $X -le 20 ]
do
echo $X
X=$((X+1))
done

这样导致这样的疑问: 为什么bash不能使用C风格的for循环呢?

for (X=1,X<10; X++)

这也跟bash自身的特性有关,之所以不允许这种for循环是由于:bash是一种解释性语言,因此其运行效率比较低。也正是由于这个原因,高负荷迭代是不允许的。

命令替换

Bash shell有个非常好用的特性叫做命令替换。允许我们将一个命令的输出当做另一个命令的输入。比如你想要将命令的输出赋值给变量X,你可以通过变量替换来实现。

有两种命令替换的方式:大括号扩展和反撇号扩展。

大括号扩展: $(commands) 会展开为命令commands的输出结果。并且允许嵌套使用,所以commands中允许包含子大括号扩展。

反撇好扩展:将commands扩展为命令commands的输出结果。不允许嵌套。

这里有一个例子:

1
2
3
4
5
6
7
#!/bin/bash
files="$(ls)"
web_files=`ls public_html`
echo "$files" # we need the quotes to preserve embedded newlines in $files
echo "$web_files" # we need the quotes to preserve newlines
X=`expr 3 * 2 + 4` # expr evaluate arithmatic expressions. man expr for details.
echo "$X"

$()替换方式的优点不言自明:非常易于嵌套。并且大多数bourne shell的衍生版本都支持(POSIX shell 或者更好的都支持)。不过,反撇号替换更简单明了,即使是最基本的shell它也提供了支持(任意版本的#!/bin/sh都可以)

shell脚本入门笔记的更多相关文章

  1. Linux Shell脚本入门--cut命令

    Linux Shell脚本入门--cut命令 cut cut 命令可以从一个文本文件或者文本流中提取文本列. cut语法 [root@www ~]# cut -d'分隔字符' -f fields &l ...

  2. linux的shell脚本入门

    Linux shell脚本入门教程 为什么要进行shell编程 在Linux系统中,虽然有各种各样的图形化接口工具,但是sell仍然是一个非常灵活 的工具.Shell不仅仅是命令的收集,而且是一门非常 ...

  3. (一)shell脚本入门

    shell脚本入门 1.脚本格式 脚本以#!/bin/bash 开头(指定解析器) 2.第一个shell脚本:helloworld (1)需求:创建一个shell脚本,输出helloworld 运行: ...

  4. Linux Shell脚本入门--wget 命令用法详解

    Linux Shell脚本入门--wget 命令用法详解 wget是在Linux下开发的开放源代码的软件,作者是Hrvoje Niksic,后来被移植到包括Windows在内的各个平台上.它有以下功能 ...

  5. 【shell】shell脚本入门

    1. 前言 1.1 为什么学习shell编程 Shell脚本语言是实现Linux/UNIX系统管理及自动化运维所必备的重要工具,Linux/UNIX系统的底层及基础应用软件的核心大部分涉及Shell脚 ...

  6. shell 脚本实战笔记(10)--spark集群脚本片段念念碎

    前言: 通过对spark集群脚本的研读, 对一些重要的shell脚本技巧, 做下笔记. *). 取当前脚本的目录 sbin=`dirname "$0"` sbin=`cd &quo ...

  7. Linux入门第五天——shell脚本入门(上)基本概念

    一.什么是shell脚本 Shell 脚本(shell script),是一种为 shell 编写的脚本程序. 二.shell入门 1.先导知识 变量知识补充:https://www.cnblogs. ...

  8. shell脚本自学笔记

    一. 什么是Shell脚本 shell脚本并不能作为正式的编程语言,因为它是在linux的shell中运行的,所以称为shell脚本.事实上,shell脚本就是一些命令的集合. 假如完成某个需求需要一 ...

  9. 简单的 Shell 脚本入门教程

    Shell脚本 运作方式与解释型语言相当,如果有语言基础,学起 Shell 脚本就非常容易,但是 Shell 与常见的语言不同,一些常见的函数在 Shell 中需要组合一些命令得以实现 工具推荐 Sh ...

随机推荐

  1. JS高级---遍历DOM树

    遍历DOM树  第一个函数: 给我根节点, 我会找到所有的子节点: forDOM(根节点)  获取这个根节点的子节点  var children=根节点的.children  调用第二个函数  第二个 ...

  2. 意外发现--http-server使用

    http-server 在很多情况下,需要在本地开启http服务器来测试.所以就需要一个简单的省事好用的http服务器.以前的时候,都是使用php的本地环境,但是,自从学了nodejs,发现了http ...

  3. Redis Desktop Manager 连接不上redis的问题

    1.需要启动redis,进入后测试,ping,回应pong,说明redis可用 启动redis的代码: redis-server /myredis/redis.conf redis-cli 如果还是连 ...

  4. [Web安全]SQL注入

    Web网站最头痛的就是遭受攻击.Web很脆弱,所以基本的安防工作,我们必须要了解! 所谓SQL注入,就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意 ...

  5. 微信小程序 selectComponent 值为null

    这个东西的执行时间感觉有点迷, 我遇到的情况是在page 的onReady  onShow 当中 使用 selectComponent 无法获取到子组件的对象 只好退而求其次  在需要触发的方法当中 ...

  6. EF中的实体关系

    导航属性的理解: 指数据库的表所对应的实体类,除了要有每个字段所对应的属性之外,还应该有一个与之有关联的表的属性,一对一的关系就是关联表的类型,一对多的关系就是关联表的类型的ICollection的泛 ...

  7. linq和扩展方法

    c#的扩展方法 1.必须是在一个非嵌套.非泛型的静态类中的静态方法 2.至少一个参数,第一个参数附加this,不能有其他修饰符如out.ref 3.第一个参数不能是指针类型 上面例子是自定义的一个扩展 ...

  8. 三、统一威胁管理(UTM)

    简介 统一威胁管理(Unified Threat Management),简称UTM. 2004年9月,IDC首度提出“统一威胁管理”的概念,即将防病毒.入侵检测和防火墙安全设备划归统一威胁管理(Un ...

  9. 将项目部署到linux环境下的Jetty

    1.将项目放到webapps文件夹下 2.进入到jetty/bin目录,有文件jetty.sh 3.运行  命令:./jetty.sh start 4.停止  命令:./jetty.sh stop

  10. 关于memset....我太难了

    众所周知memset是个清空数组的好东西 然而...它慢的要死 直接让我从30ms炸到1045ms 于是快乐tle .... 是我的错 所以以后还是手动清空 (我真快乐)