shell编程知识点集锦

1.shell脚本加分号和不加分号的区别

shell脚本是按行分隔每一条shell语句。如果每一条shell语句写在单独一行,此时可以加分号,也可以不加,没有什么区别。如果多条shell写在同一行,那么此时需一定要用分号将不用语句分隔开来。

2.>/dev/null 2>&1 这句话的含义及使用的意义

/dev/null代表空设备文件,类似于Windows的回收站。

>代表重定向到哪里,例如:echo "123" > /home/123.txt。 1表示STDOUT标准输出,是标准输出文件描述符,默认对应屏幕。系统默认值是1,所以”>/dev/null”等同于”1>/dev/null”。 2 表示STDERR标准错误,是标准错误文件描述符,默认对应屏幕。 & 表示等同于的意思,2>&1,表示2的输出重定向等同于1。

那么本文标题的语句: 1>/dev/null 首先表示标准输出重定向到空设备文件,也就是不输出任何信喜到终端,说白了就是不显示任何信息。 2>&1 接着,标准错误输出重定向等同于标准输出,因为之前标准输出已经重定向到了空设备文件,所以标准错误输出也重定向到空设备文件。

那么2>&1 >/dev/null>/dev/null 2>&1 的区别是什么呢? command >/dev/null 2>&1 相当于

stdout="/dev/null"
stderr=$stdout

这时,stderr也等于”/dev/null”了

结果是标准输出和标准错误都指向了/dev/null, 也就是所有的输出都被我们丢弃。

command 2>&1 >/dev/null 相当于

stderr=$stdout #stderr指向了屏幕,因为stdout这时还是指向屏幕!
stdout="/dev/null" #stdout指向了/dev/null,但不会影响到 stderr的指向

结果是标准错误仍然被打印到屏幕上, 而标准输出被丢弃。

3.grep的双引号和单引号的区别

grep(Global Regular Expression Print)全局正则表达式打印,是Unix和L inux中使用最广泛的命令之一。

grep的使用一般格式: grep [选项] [正则表达式] [文件] 这里正则表达式也可为字符串。

在使用的过程中,我们会看到有人用双引号将带待查找模式包围,也有人用单引号将带待查找模式包围,再使用字符串的时候,也有些人既不用单引号也不用双引号。那么单引号和双引号的区别是什么呢?

它们的区别是使用场景不同。这里与其说grep的双引号和单引号的区别,不如说是shell的单引号和双引号的使用区别,因为在使用grep进行模式查找时,还是由shell来启动grep进行字符串查找的,shell是所有linux命令的解析器。

首先说一下shell的特殊字符有哪些,我知道的有五个:单引号(’)、双引号(”)、反引号(`)、美元符号($)和反斜杠(\)。对shell来说,它们有特殊意义,除了单引号和双引号是本人讨论的重点,其它三位的作用这里举例简要说明它们的作用。

反引号(`):在shell中起到命令替换的作用。命令替换是指shell能够将一个命令的标准输出插在反引号引用的命令的位置。如:

echo "now is `date`"
#输出
now is Tue Jun 14 16:26:34 CST 2016 

美元符号($):对shell变量的引用。 反斜杠(\):取消特殊字符的特殊含义。

好了,言归正传,回到我们的单引号和双引号的区别。 单引号: 可以说是所见即所得:即将单引号内的内容原样输出,或者描述为单引号里面看到的是什么就会输出什么。单引号”是全引用,被单引号括起的内容不管是常量还是变量者不会发生替换。

注意:这里大家很容易产生误解的就是单引号括起来的内容不就是一个常量字符串了,是的。对于这个常量字符串,grep又会对其进行正则表达式的解析来查找想要匹配的文本。

问题来了,如果查找的内容是正则表达式的特殊字符该怎么呢?比如我们要查找包含字符^的字符串,但是字符^又是正则表达式中的特殊字符。这个时候,我们可以使用反斜杠(\)进行转义就可以了。

#查找以字符r开头的行
echo "t^root"|grep '^r'

#查找包含^r的行
echo "t^root"|grep '\^r'

双引号: 双引号引用的内容,所见非所得。如果内容中有命令、变量等,会先把变量、命令解析出结果,然后在输出最终内容来。双引号”“是部分引用,被双引号括起的内容常量还是常量,变量则会发生替换,替换成变量内容。

不加引号: 不会将含有空格的字符串视为一个整体输出。如果内容中有命令、变量等,会先把变量、命令解析出结果,然后在输出最终内容来。如果字符串中带有空格等特殊字符,则不能完整的输出,需要改加双引号,一般连续的字符串,数字,路径等可以用。

比如查找字符串”jet plane”时,如果不用双引号将其括起来,那么单词plane将被误认为是一个文件,查询结果将返回“文件不存在”的错误信息。”

使用规则: 针对grep,查找一般常量字符串用单引号”括起,如果含有变量则用双引号”“括起。注意,正则表达式也是字符串常量。

针对shell变量,一般常量字符串使用单引号,包含有变量的则用双引号。

总之,尽量不要不加引号。

4.shell脚本中typeset的几点疑问

无选项的执行typeset作用是什么?例如下面的脚本:

typeset USAGE="Usage: $0 ConfigFile Date hour
Example: $0 config.conf 20070716 23";
readonly USAGE;

对变量USAGE使用typeset的作用是什么?

5.Shell中包含、调用、引用另一个脚本文件的三种方法

脚本 first.sh:

#!/bin/bash
echo 'your are in first file'

方法一:使用source

#!/bin/bash
echo 'your are in second file'
source first

方法二:使用点号.

#!/bin/bash
echo 'your are in second file'
. first

注意,点号与脚本文件之间记得要有空格。

方法三:使用sh

#!/bin/bash
echo 'your are in second file'
sh  first

三者输出的结果都是:

your are in second file
your are in first file

但是第三个使用sh命令来调用另外的脚本和前面两种方法有着本质的区别。使用source命令和点号.是等价了,类似于CC++中的include预处理指令,都是将指定的脚本内容拷贝至当前的脚本中,由一个shell进程来执行。但是使用sh命令则会开启新的shell进程来执行指定的脚本,这样的话,父进程中的变量在子进程中就无法访问。参考如下代码: first.sh内容如下,访问了second.sh中的变量second。

#!/bin/bash
echo 'your are in first file'
echo 'second:' $second

second.sh内容,通过上面介绍的三种方法来调用first.sh,看看对second.sh的变量second访问有什么区别!

#!/bin/bash
second=lvlv
echo 'your are in second file'
source first
. first
sh  first

程序的输出结果是:

your are in second file
your are in first file
second: lvlv
your are in first file
second: lvlv
your are in first file
second:

可见,使用sh命令开启一个子进程来调用指定的shell脚本无法访问父进程的变量。我们如何让子进程访问父进程中变量呢?可以使用export命令。

说到export命令,我们需要知道shell中按照变量的作用域和生命周期,shell变量可分为四大类: (1)永久环境变量:需要修改配置文件,变量永久生效。 (2)临时环境变量:使用export命令行声明即可,变量在shell脚本进程结束后仍然有效,但在关闭当前shell会话后失效。 (3)全局变量:在脚本中定义,仅在当前shell脚本中有效,其他shell脚本进程不能访本,其作用域从定义的位置开始,到脚本结束或被显示删除的地方为止。注意,全局变量既可以在shell函数内定义,也可以在shell函数外定义,因为shell函数内定义的变量默认为global,且作用域从“函数被调用时执行变量定义的地方”开始,到脚本结束或被显示删除的地方为止。 (4)局部变量。在shell脚本中函数内显示使用local关键字定义的变量。其作用域局限于函数内。同名local变量会屏蔽global变量。

所以,使用export命令我们申明的是临时环境变量,在当前shell会话中,所有的shell实例都可以访问由export命令申明的临时环境变量。因为当前shell会话中的所有shell实例,都是当前shell会话的子进程,所以可以与父进程的一同访问环境变量。

那么如何定义永久环境变量呢?可以采用如下两种方法: (1) 在/etc/profile文件中添加变量【对所有用户生效(永久的)】

用VI在文件/etc/profile文件中增加变量,该变量将会对Linux下所有用户有效,并且是“永久的”。

例如:编辑/etc/profile文件,添加CLASSPATH变量

#vi /etc/profile
export CLASSPATH=./JAVA_HOME/lib;$JAVA_HOME/jre/lib

注:修改文件后要想马上生效还要运行# source /etc/profile不然只能在下次重进此用户时生效。

(2) 在用户目录下的.bash_profile文件中增加变量【对单一用户生效(永久的)】

用VI在用户目录下的.bash_profile文件中增加变量,改变量仅会对当前用户有效,并且是“永久的”。

例如:编辑guok用户目录(/home/guok)下的.bash_profile

$ vi /home/guok/.bash.profile

#添加如下内容:
export CLASSPATH=./JAVA_HOME/lib;$JAVA_HOME/jre/lib

注:修改文件后要想马上生效还要运行$ source /home/guok/.bash_profile不然只能在下次重进此用户时生效。

另外: (1)使用readonly命令可设置只读变量。如果使用了readonly命令的话,变量就不可以被修改或清除了。

(2)使用unset命令来清除环境变量 $unset TEMP_KEVIN #删除环境变量TEMP_KEVIN

6.shell中$()、反引号和${}的区别

$()和反引号“的作用相同,用于命令替换(command substitution),即完成引号里的命令行,将其结果替换出来,与变量替换差不多。比如:

echo `date '--date=1 hour ago' +%Y-%m-%d-%H`
#或者
echo $(date '--date=1 hour ago' +%Y-%m-%d-%H)

输出结果是相同的:2016-06-28-09。

建议使用$(),原因有二: (1)反引号与单引号外形相似,容易混淆; (2)在多层次的复合替换中,反引号需要跳脱( /` )处理,而$()则比较直观。例如下面的命令格式是错的:

command1 `command2 `command3` `

原本的意图是要在`command2 `command3` `中先将command3替换出来给command2处理,然后再将结果传给command1处理。然而,真正的结果在命令行中却是分成了`command2`与` `两段。正确的输入应该如下:

command1 `command2 /`command3/``
#或者换成$()
command1 $(command2 $(command3))

${}用于变量替换。 一般情况下,$var${var}并没有啥不一样。但是用${ }会比较精确的界定变量名称的范围,比方说:

A=B
echo $AB

原本是打算先将$A的结果替换出来,然后再补一个B字母于其后,但在命令行上,真正的结果却是只会替换变量名称为AB的值出来。若使用${}就没问题了:

echo ${A}B

除此之外,${} 还有一些特殊功能。 假设我们定义了一个变量为:

file=/dir1/dir2/dir3/my.file.txt

我们可以用${} 按照指定字符提取字符串:

${file#*/}:拿掉第一条/及其左边的字符串:dir1/dir2/dir3/my.file.txt
${file##*/}:拿掉最后一条/及其左边的字符串:my.file.txt
${file#*.}:拿掉第一个. 及其左边的字符串:file.txt
${file##*.}:拿掉最后一个. 及其左边的字符串:txt
${file%/*}:拿掉最后条/及其右边的字符串:/dir1/dir2/dir3
${file%%/*}:拿掉第一条/及其右边的字符串:(空值)
${file%.*}:拿掉最后一个. 及其右边的字符串:/dir1/dir2/dir3/my.file
${file%%.*}:拿掉第一个. 及其右边的字符串:/dir1/dir2/dir3/my

记忆的方法为:

#是去掉左边(在鉴盘上#在$之左边)
%是去掉右边(在鉴盘上%在$之右边)
单一符号是最小匹配﹔两个符号是最大匹配。

字符串的提取: ${file:0:5}:提取最左边的5个字节:/dir1 ${file:5:5}:提取第5个字节右边的连续5个字节:/dir2

变量值里的字符串作替换:

${file/dir/path}:将第一个dir替换为path:/path1/dir2/dir3/my.file.txt
${file//dir/path}:将全部dir替换为path:/path1/path2/path3/my.file.txt

7.shell脚本的执行顺序

shell脚本是从上到下,按照顺序一条一条解析命令。如果当前命令没有返回则阻塞等待,直到当前命令执行完成后才继续执行下一条命令。

可以使用“&”把一个程序的执行放入后台,但是当脚本运行到最后是不会等待这个进程的返回结果的,所以会直接结束脚本运行,该进程也会成为一个孤儿。解决方法是在脚本最后放“wait”。

总结: shell脚本的执行就和手动一行一行打入一样;可以用&把它放到后台,这样就不需要等上一行命令结束就可以继续执行。

8.shell函数的几点疑问

(1)shell函数的定义方式 可以带function fun() 定义,也可以直接fun() 定义,且不带任何参数,函数名必须唯一,使用使时可以传递参数,使用$1,$2,...,$* 来获取参数。建议使用function关键字来定义函数,便于代码阅读。定义如下:

function myFunc(){
    echo "lvlv"
    return 0
}

(2)shell函数的调用方式 要在脚本中调用函数, 只需给出函数名。比如要调用上面的myFunc(), 只需给出 myFunc。shell函数的使用形式上与shell命令很相似!

(3)shell函数如何获取函数返回值 使用$? 获取。

(4)shell函数使用时需要前置申明吗? shell函数在使用前必须定义,没有申明的说法!一个通常的办法是把函数定义放在脚本开始部分。

9.shell单行注释与多行注释

(1)单行注释 众所周知,shell中使用 # 比如想要注释:echo “ni”

#echo "this has been annotated"

(2)多行注释 方法一:

:<<!
语句1
语句2
!

方法二:

:'
语句1
语句2
'

方法三:

if false; then
语句1
语句2
fi

方法四:

:<<[字符]  #这里的字符可以是数字或者是字符都可以
语句1
语句2
[字符]                        

方法五:

((0)) & {
语句1
语句2
}

10.shell中if匹配正则表达式

shell编程中,我们可以使用双中括号运算符[[]]和=~来判断字符串是否匹配给定的正则表达式,例如匹配以lvlv结尾的字符串:

filelist="lvlvcheck dablelvlv checklvlv"
for file in $filelist
do 
    if [[ $file =~ lvlv$ ]]
    then
        echo $file
    fi
done

输出:

dablelvlv 
checklvlv

注意事项:if [[ $file =~ lvlv$ ]] 中注意有五个空格,而且正则表达式不能使用单引号或者双引号,否则会被当做普通字符串。

这里要吐槽一下,shell真的很强大,但是语法又过于苛刻晦涩,少一个空格有时都能让人抓狂,真的很坑爹啊!

如果想使shell if不匹配指定的正则表达式,可以shell的逻辑运算符感叹号!,同时还是要注意空格,示例如下:

if [[ ! $file =~ check$ ]]

11.shell中exit和return的区别

功能层面: exit用于退出当前shell脚本进程,像操作系统或者父进程返回当前shell脚本进程退出状态,状态码取值范围是0-255,POSIX规定的几种退出状态如下:

退出状态

含义

0

运行成功

1~125

各种运行失败

126

找到命令,但无法执行

127

未找到运行的命令

>128

命令被系统强行结束

编程语言层面: exit是一个系统命令,用于在程序运行的过程中随时结束进程,它会删除进程占用的内存空间,并将status是返回给父进程,这个status通常用于标识程序的一些运行信息。

return是语言级别的一个关键字,它表示调用堆栈的返回,用于带一个status从函数退出。return不带参数时,则会返回函数体中最后一个命令的返回值。 return 也可以用于. (source) 方式(子shell)执行脚本时的返回,也可以返回指定的status 或者脚本中最后一个命令的exit status。

return 不用于函数体,也没有以.(source)方式执行脚本时,则会报错。所以正确的用法是将return 用于函数的返回,exit用于进程的退出。比如如果主函数调用子函数,在子函数里使用return会回到主函数中,但在子函数中误用exit则会直接退出进程。


参考文献

[1]grep后加单引号、双引号和不加引号的区别 [2]设置环境变量永久生效和临时生效 export PS1 [3]Shell中脚本变量和函数变量的作用域 [4]Linux&shell之高级Shell脚本编程-创建函数 [5]shell函数几个要点 [6]shell中if条件字符串、数字比对、[[ ]]和[ ]区别 [7]exit-shell退出状态

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏武军超python专栏

2018-7-16python中四种组合数据类型和pycharm的安装和使用

集合(set) discard删除数据时如果集合里面没有那个数据什么也不做,集合相减可以直接用-,+*/都不能用

1785
来自专栏大前端_Web

详解ES7的async及webpack配置async

版权声明:本文为吴孔云博客原创文章,转载请注明出处并带上链接,谢谢。 https://blog.csdn.net/wkyseo/articl...

5182
来自专栏黑白安全

C++如何调用class类中方法实现多线程编程

众所周知在使用C++创建多线程执行时只能传递一个方法到thread模块中去创建线程执行。但是有时候我们往往需要使用多线程去执行某个对象中的方法,而对象中的方法却...

1242
来自专栏mwangblog

bash中的变量

1308
来自专栏西二旗一哥

iOS - Dissecting objc_msgSend on ARM64

我们的朋友 ldp 又出现了。这回它读取了 x12 中指针,这个指针指向了要查找的bucket。每个bucket包含了一个选择器和一个 IMP 。 x9 现在包...

1224
来自专栏C语言及其他语言

每日一题[C语言程序设计教程(第三版)课后习题1.5]

请参照本章例题,编写一个C程序,输出以下信息: ************************** Hello World! ****************...

2723
来自专栏http://www.cnblogs.com

shelve模块

shelve是一个简单的数据存储方案,类似key-value数据库,可以很方便的保存python对象,其内部是通过pickle协议来实现数据序列化。shelve...

37317
来自专栏Python小屋

Python编程常见出错信息及原因分析(1)

1.被0除错误 演示代码: >>> 2 / 0 Traceback (most recent call last): File "<pyshell#0>",...

3056
来自专栏大闲人柴毛毛

Java并发编程的艺术(二)——重排序

当我们写一个单线程程序时,总以为计算机会一行行地运行代码,然而事实并非如此。 什么是重排序? 重排序指的是编译器、处理器在不改变程序执行结果的前提下,重新排列指...

38410
来自专栏cnblogs

knockout源码分析之computed(依赖属性)

一、序列图 ? 二、主要代码文件 1、dependentObservable.js:主要包含ko.computed相关方法的处理 2、dependencyDet...

2195

扫码关注云+社区

领取腾讯云代金券