【Linux】shell脚本教程

news2024/11/25 20:27:29

目录

一、shell历史

二、执行脚本

三、基本语法

3.1变量

3.1.1变量的分类

3.1.2删除变量

3.2文件名代换(Globbing)

3.3命令代换

3.4算术代换

3.5转义字符

3.6单引号

3.7双引号

四、Shell脚本语法

4.1条件测试

4.2分支

4.2.1if/then/elif/else/fi

4.2.2case/esac

4.3循环

4.3.1for/do/done

4.3.2while/do/done

4.3.3break和continue

​​​​​​​4.4位置参数和特殊变量

4.5输入输出

4.5.1echo

​​​​​​​4.5.2管道

​​​​​​​4.5.3tee

​​​​​​​4.6函数

​​​​​​​五、Shell脚本调试方法

​​​​​​​六、正则表达式

​​​​​​​6.1基本语法

​​​​​​​6.1.1字符类

​​​​​​​6.1.2数量限定符

​​​​​​​6.1.3位置限定符

​​​​​​​6.1.4其它特殊字符

​​​​​​​​​​​​​​6.2Basic正则和Extended正则区别


一、shell历史​​​​​​​

        Shell的作用是解释执行用户的命令,用户输入一条命令,Shell就解释执行一条,这种方式称为交互式(Interactive),Shell还有一种执行命令的方式称为批处理(Batch),用户事先写一个Shell脚本(Script),其中有很多条命令,让Shell一次把这些命令执行完,而不必一条一条地敲命令。

        Shell脚本和编程语言很相似,也有变量和流程控制语句,但Shell脚本是解释执行的,不需要编译,Shell程序从脚本中一行一行读取并执行这些命令,相当于一个用户把脚本中的命令一行一行敲到Shell提示符下执行。  

        shell是用户和 Linux 内核之间的接口程序;用户在提示符下输入的命令都由 shell先解释然后传给 Linux 核心;它调用了系统核心的大部分功能来执行程序、并以并行的方式协调各个程序的运行。

 shell 是用户跟内核通信几种方式的一种

由于历史原因,UNIX系统上有很多种Shell:

  1. sh(Bourne Shell):由Steve Bourne开发,各种UNIX系统都配有sh。
  2. csh(C Shell):由Bill Joy开发,随BSD UNIX发布,它的流程控制语句很像C语言,支持很多Bourne Shell所不支持的功能:作业控制,命令历史,命令行编辑。
  3. ksh(Korn Shell):由David Korn开发,向后兼容sh的功能,并且添加了csh引入的新功能,是目前很多UNIX系统标准配置的Shell,在这些系统上/bin/sh往往是指向/bin/ksh的符号链接。
  4. tcsh(TENEX C Shell):是csh的增强版本,引入了命令补全等功能,在FreeBSD、MacOS X等系统上替代了csh。
  5. bash(Bourne Again Shell):由GNU开发的Shell,主要目标是与POSIX标准保持一致,同时兼顾对sh的兼容,bash从csh和ksh借鉴了很多功能,是各种Linux发行版标准配置的Shell,在Linux系统上/bin/sh往往是指向/bin/bash的符号链接。虽然如此,bash和sh还是有很多不同的,一方面,bash扩展了一些命令和参数,另一方面,bash并不完全和sh兼容,有些行为并不一致,所以bash需要模拟sh的行为:当我们通过sh这个程序名启动bash时,bash可以假装自己是sh,不认扩展的命令,并且行为与sh保持一致。

        一般默认使用 bash 作为默认的解释器。我们后面编写的 shell脚本,都是由上述 shell命令解释器解释执行的。

查看用户shell类型

itcast$ vim /etc/passwd

        其中最后一列显示了用户对应的shell类型

        root:x:0:0:root:/root:/bin/bash

        nobody:x:65534:65534:nobody:/nonexistent:/bin/sh

        syslog:x:101:103::/home/syslog:/bin/false

        itcast:x:1000:1000:itcast,,,:/home/itcast:/bin/bash

        ftp:x:115:125:ftp daemon,,,:/srv/ftp:/bin/false

shell 脚本大体可以分为两类:

系统进行调用: 这类脚本无需用户调用,系统会在合适的时候调用,如:/etc/profile、~/.bashrc 等 /etc/profile 此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行, 系统的公共环境变量在这里设置; 开始自启动的程序,一般也在这里设置 ~/.bashrc 用户自己的家目录中的.bashrc 登录时会自动调用,打开任意终端时也会自动调用 这个文件一般设置与个人用户有关的环境变量,如交叉编译器的路径等等

用户编写:需要手动调用的 例如我们上面编写的脚本都属于此类 无论是系统调用的还是需要我们自己调用的,其语法规则都一样

二、执行脚本

编写一个简单的脚本test.sh:

#!/bin/sh

echo HelloWorld

cd ..

ls

        Shell脚本中用#表示注释,相当于C语言的//注释。但如果#位于第一行开头,并且是#!(称为Shebang)则例外,它表示该脚本使用后面指定的解释器/bin/sh解释执行。如果把这个脚本文件加上可执行权限然后执行:

itcast$ chmod a+x test.sh

itcast$ ./test.sh

        Shell会fork一个子进程并调用exec执行./test.sh这个程序,exec系统调用应该把子进程的代码段替换成./test.sh程序的代码段,并从它的_start开始执行。然而test.sh是个文本文件,根本没有代码段和_start函数,怎么办呢? 其实exec还有另外一种机制,如果要执行的是一个文本文件,并且第一行用Shebang指定了解释器,则用解释器程序的代码段替换当前进程,并且从解释器的_start开始执行,而这个文本文件被当作命令行参数传给解释器。因此,执行上述脚本相当于执行程序。

 itcast$ /bin/sh ./test.sh

以这种方式执行不需要test.sh文件具有可执行权限。

        如果将命令行下输入的命令用()括号括起来,那么也会fork出一个子Shell执行小括号中的命令,一行中可以输入由分号;隔开的多个命令,比如:

itcast$ (cd ..;ls -l)

        和上面两种方法执行Shell脚本的效果是相同的,cd ..命令改变的是子Shell的PWD,而不会影响到交互式Shell。然而命令

itcast$ cd ..;ls -l

        则有不同的效果,cd ..命令是直接在交互式Shell下执行的,改变交互式Shell的PWD,然而这种方式相当于这样执行Shell脚本:

itcast$ source ./test.sh

或者

itcast$ . ./test.sh

        source或者.命令是Shell的内建命令,这种方式也不会创建子Shell,而是直接在交互式Shell下逐行执行脚本中的命令。

三、基本语法

3.1变量

        按照惯例,Shell变量通常由字母加下划线开头,由任意长度的字母、数字、下划线组成

在Shell中定义或赋值一个变量:

VARNAME=value

        注意等号两边都不能有空格,否则会被Shell解释成命令和命令行参数。

        变量的使用,用$符号跟上变量名表示对某个变量取值,变量名可以加上花括号来表示变量名的范围:

echo $VARNAME

echo ${VARNAME}_suffix   #使用花括号来分离VARNAME和_suffix,不至于把VARNAME_suffix当做变量名

3.1.1变量的分类

根据变量的作用域不同,shell分为两种变量

        1)环境变量

        环境变量可以从父进程传给子进程,因此Shell进程的环境变量可以从当前Shell进程传给fork出来的子进程。用printenv命令可以显示当前Shell进程的环境变量。注意:环境变量只能从父进程传递给子进程而子进程不能够传给父进程。

        2)本地变量

        只存在于当前Shell进程,用set命令可以显示当前Shell进程中定义的所有变量(包括本地变量和环境变量)和函数。

        环境变量是任何进程都有的概念,而本地变量是Shell特有的概念。在Shell中,环境变量和本地变量的定义和用法相似。

        一个变量定义后仅存在于当前Shell进程,它是本地变量,用export命令可以把本地变量导出为环境变量,定义和导出环境变量通常可以一步完成:

itcast$ export VARNAME=value

也可以分两步完成:

itcast$ VARNAME=value

itcast$ export VARNAME

本地变量根据作用于,也分为全局的本地变量以及局部的本地变量

        全局的本地变量在整个shell文件中都可以访问,比如直接声明和定义一个变量var=value其实就是一个全局的本地变量,但是如果在shell定义的函数里边可以使用local 来声明一个局部变量,这种变量作用于仅限于函数内

function test

{

        local var=”this is a local variable”

}

3.1.2删除变量

        用unset命令可以删除已定义的环境变量或本地变量。

itcast$ unset VARNAME

        如果一个变量叫做VARNAME,用 ' VARNAME ' 可以表示它的值,在不引起歧义的情况下也可以用VARNAME表示它的值。通过以下例子比较这两种表示法的不同:

itcast$ echo $SHELL

        注意,在定义变量时不用“'”取变量值时要用。和C语言不同的是,Shell变量不需要明确定义类型,是一种弱类型的语言,事实上Shell变量的值都是字符串,比如我们定义VAR=45,其实VAR的值是字符串45而非整数。Shell变量不需要先定义后使用,如果对一个没有定义的变量取值,则值为空字符串。

3.2文件名代换(Globbing)

这些用于匹配的字符称为通配符(Wildcard),如:* ? [ ] 具体如下:

* 匹配0个或多个任意字符 

? 匹配一个任意字符

[若干字符] 匹配方括号中任意一个字符的一次出现

itcast$ ls /dev/ttyS*

itcast$ ls ch0?.doc

itcast$ ls ch0[0-2].doc

itcast$ ls ch[012] [0-9].doc

        注意,Globbing所匹配的文件名是由Shell展开的,也就是说在参数还没传给程序之前已经展开了,比如上述ls ch0[012].doc命令,如果当前目录下有ch00.doc和ch02.doc,则传给ls命令的参数实际上是这两个文件名,而不是一个匹配字符串。

3.3命令代换

        由“`”反引号括起来的也是一条命令,Shell先执行该命令,然后将输出结果立刻代换到当前命令行中。例如定义一个变量存放date命令的输出:

itcast$ DATE=`date`

itcast$ echo $DATE

命令代换也可以用$()表示:

itcast$ DATE=$(date)

3.4算术代换

使用$(()),用于算术计算,(())中的Shell变量取值将转换成整数,同样含义的$[ ]等价例如:

itcast$ VAR=45

itcast$ echo $(($VAR+3))         等价于    $((var+3)) 或echo $[VAR+3]或 $[$VAR+3]

$(())中只能用+-*/和()运算符,并且只能做整数运算。

$[base#n],其中base表示进制,n按照base进制解释,后面再有运算数,按十进制解释。

echo $[8#10+11]

echo $[16#10+11]

3.5转义字符

        和C语言类似,\在Shell中被用作转义字符,用于去除紧跟其后的单个字符的特殊意义(回车除外),换句话说,紧跟其后的字符取字面值。例如:

itcast$ echo $SHELL

/bin/bash

itcast$ echo \$SHELL

$SHELL

itcast$ echo \\

\

比如创建一个文件名为“$ $”的文件($间含有空格)可以这样:

itcast$ touch \$\ \$

        还有一个字符虽然不具有特殊含义,但是要用它做文件名也很麻烦,就是-号。如果要创建一个文件名以-号开头的文件,这样是不正确的:

itcast$ touch -hello

touch: invalid option -- h

Try `touch --help' for more information.

即使加上\转义也还是报错:

itcast$ touch \-hello

touch: invalid option -- h

Try `touch --help' for more information.

        因为各种UNIX命令都把-号开头的命令行参数当作命令的选项,而不会当作文件名。如果非要处理以-号开头的文件名,可以有两种办法:

itcast$ touch ./-hello

或者

itcast$ touch -- -hello

        \还有一种用法,在\后敲回车表示续行,Shell并不会立刻执行命令,而是把光标移到下一行,给出一个续行提示符>,等待用户继续输入,最后把所有的续行接到一起当作一个命令执行。例如:

itcast$ ls \

> -l

(ls -l命令的输出)

3.6单引号

        和C语言不同,Shell脚本中的单引号和双引号都是字符串的界定符(双引号下一节介绍),而不是字符的界定符。单引号用于保持引号内所有字符的字面值,即使引号内的\和回车也不例外,但是字符串中不能出现单引号。如果引号没有配对就输入回车,Shell会给出续行提示符,要求用户把引号配上对。例如:

itcast$ echo '$SHELL'

$SHELL

itcast$ echo 'ABC\(回车)

> DE'(再按一次回车结束命令)

ABC\

DE

3.7双引号

        被双引号括住的内容,将被视为单一字串。它防止通配符扩展,但允许变量扩展。这点与单引号的处理方式不同

itcast$ DATE=$(date)

itcast$ echo "$DATE"

itcast$ echo '$DATE'

再比如:

itcast$ VAR=200

itcast$ echo $VAR  

200

itcast$ echo '$VAR'

$VAR

itcast$ echo "$VAR"

200

四、Shell脚本语法

4.1条件测试

        命令test或 [ 可以测试一个条件是否成立,如果测试结果为真,则该命令的Exit Status为0,如果测试结果为假,则命令的Exit Status为1(注意与C语言的逻辑表示正好相反)。例如测试两个数的大小关系:

itcast@ubuntu:~$ var=2

itcast@ubuntu:~$ test $var -gt 1

itcast@ubuntu:~$ echo $?

0

itcast@ubuntu:~$ test $var -gt 3

itcast@ubuntu:~$ echo $?

1

itcast@ubuntu:~$ [ $var -gt 3 ]

itcast@ubuntu:~$ echo $?

1

itcast@ubuntu:~$

        虽然看起来很奇怪,但左方括号 [ 确实是一个命令的名字,传给命令的各参数之间应该用空格隔开,比如:$VAR、-gt、3、] 是 [ 命令的四个参数,它们之间必须用空格隔开。命令test或 [ 的参数形式是相同的,只不过test命令不需要 ] 参数。以 [ 命令为例,常见的测试命令如下表所示:

[ -d DIR ] 如果DIR存在并且是一个目录则为真

[ -f FILE ] 如果FILE存在且是一个普通文件则为真

[ -z STRING ] 如果STRING的长度为零则为真

[ -n STRING ] 如果STRING的长度非零则为真

[ STRING1 = STRING2 ] 如果两个字符串相同则为真

[ STRING1 == STRING2 ] 同上

[ STRING1 != STRING2 ] 如果字符串不相同则为真

[ ARG1 OP ARG2 ] ARG1和ARG2应该是整数或者取值为整数的变量,OP是-eq(等于)-ne(不等于)-lt(小于)-le(小于等于)-gt(大于)-ge(大于等于)之中的一个

和C语言类似,测试条件之间还可以做与、或、非逻辑运算:

[ ! EXPR ] EXPR可以是上表中的任意一种测试条件,!表示“逻辑反(非)”

[ EXPR1 -a EXPR2 ] EXPR1和EXPR2可以是上表中的任意一种测试条件,-a表示“逻辑与”

[ EXPR1 -o EXPR2 ] EXPR1和EXPR2可以是上表中的任意一种测试条件,-o表示“逻辑或”

例如:

$ VAR=abc

$ [ -d Desktop -a $VAR = 'abc' ]

$ echo $?

0

        注意,如果上例中的$VAR变量事先没有定义,则被Shell展开为空字符串,会造成测试条件的语法错误(展开为[ -d Desktop -a  = ‘abc’ ]),作为一种好的Shell编程习惯应该总是把变量取值放在双引号之中(展开为[ -d Desktop -a “” = ‘abc’ ]):

$ unset VAR

$ [ -d Desktop -a $VAR = 'abc' ]

bash: [: too many arguments

$ [ -d Desktop -a "$VAR" = 'abc' ]

$ echo $?

4.2分支

4.2.1if/then/elif/else/fi

        和C语言类似,在Shell中用if、then、elif、else、fi这几条命令实现分支控制。这种流程控制语句本质上也是由若干条Shell命令组成的,例如先前讲过的

if [ -f ~/.bashrc ]; then

        . ~/.bashrc

fi

        其实是三条命令,if [ -f ∼/.bashrc ]是第一条,then . ∼/.bashrc是第二条,fi是第三条。如果两条命令写在同一行则需要用;号隔开,一行只写一条命令就不需要写;号了,另外,then后面有换行,但这条命令没写完,Shell会自动续行,把下一行接在then后面当作一条命令处理。和[命令一样,要注意命令和各参数之间必须用空格隔开。

        if命令的参数组成一条子命令,如果该子命令的Exit Status为0(表示真),则执行then后面的子命令,如果Exit Status非0(表示假),则执行elif、else或者fi后面的子命令。if后面的子命令通常是测试命令,但也可以是其它命令。Shell脚本没有{}括号,所以用fi表示if语句块的结束。见下例:

#! /bin/sh

if [ -f /bin/bash ]

then

        echo "/bin/bash is a file"

else

        echo "/bin/bash is NOT a file"

fi

if :; then echo "always true"; fi

        “:”是一个特殊的命令,称为空命令,该命令不做任何事,但Exit Status总是真。此外,也可以执行/bin/true或/bin/false得到真或假的Exit Status。再看一个例子:

#! /bin/sh

echo "Is it morning? Please answer yes or no."

read YES_OR_NO

if [ "$YES_OR_NO" = "yes" ]; then

        echo "Good morning!"

elif [ "$YES_OR_NO" = "no" ]; then

        echo "Good afternoon!"

else

        echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."

fi

上例中的read命令的作用是等待用户输入一行字符串,将该字符串存到一个Shell变量中。

        此外,Shell还提供了&&和||语法,和C语言类似,具有Short-circuit特性,很多Shell脚本喜欢写成这样:

test "$(whoami)" != 'root' && (echo you are using a non-privileged account;)

        &&相当于“if…then…”,而||相当于“if not…then…”。&&和||用于连接两个命令,而上面讲的-a和-o仅用于在测试表达式中连接两个测试条件,要注意它们的区别,例如:

test "$VAR" -gt 1 -a "$VAR" -lt 3

和以下写法是等价的

test "$VAR" -gt 1 && test "$VAR" -lt 3

4.2.2case/esac

        case命令可类比C语言的switch/case语句,esac表示case语句块的结束。C语言的case只能匹配整型或字符型常量表达式,而Shell脚本的case可以匹配字符串和Wildcard,每个匹配分支可以有若干条命令,末尾必须以;;结束,执行时找到第一个匹配的分支并执行相应的命令,然后直接跳到esac之后,不需要像C语言一样用break跳出。

#! /bin/sh

echo "Is it morning? Please answer yes or no."

read YES_OR_NO

case "$YES_OR_NO" in

yes|y|Yes|YES)

        echo "Good Morning!";;

[nN][Oo])

        echo "Good Afternoon!";;

*)

        echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."

        return 1;;

esac

        使用case语句的例子可以在系统服务的脚本目录/etc/init.d中找到。这个目录下的脚本大多具有这种形式(以/etc/init.d/nfs-kernel-server为例):

case "$1" in

        start)

                 ...

        ;;

        stop)

                 ...

        ;;

        reload | force-reload)

                 ...

        ;;

        restart)

                 ...

        *)

            log_success_msg"Usage: nfs-kernel-server {start|stop|status|reload|force-reload|restart}"

        ;;

esac

启动nfs-kernel-server服务的命令是

$ sudo /etc/init.d/nfs-kernel-server start

        $1是一个特殊变量,在执行脚本时自动取值为第一个命令行参数,也就是start,所以进入start)分支执行相关的命令。同理,命令行参数指定为stop、reload或restart可以进入其它分支执行停止服务、重新加载配置文件或重新启动服务的相关命令。

4.3循环

4.3.1for/do/done

        Shell脚本的for循环结构和C语言很不一样,它类似于某些编程语言的foreach循环。例如:

#! /bin/sh

for FRUIT in apple banana pear; do

        echo "I like $FRUIT"

done

        FRUIT是一个循环变量,第一次循环$FRUIT的取值是apple,第二次取值是banana,第三次取值是pear。再比如,要将当前目录下的chap0、chap1、chap2等文件名改为chap0~、chap1~、chap2~等(按惯例,末尾有~字符的文件名表示临时文件),这个命令可以这样写:

$ for FILENAME in chap?; do mv $FILENAME $FILENAME~; done

也可以这样写:

$ for FILENAME in `ls chap?`; do mv $FILENAME $FILENAME~; done

​​​​​​​4.3.2while/do/done

while的用法和C语言类似。比如一个验证密码的脚本:

#! /bin/sh

echo "Enter password:"

read TRY

while [ "$TRY" != "secret" ]; do

        echo "Sorry, try again"

        read TRY

done

制循环的次数:

#! /bin/sh

COUNTER=1

while [ "$COUNTER" -lt 10 ]; do

        echo "Here we go again"

        COUNTER=$(($COUNTER+1))

done

        另,Shell还有until循环,类似C语言的do…while。如有兴趣可在课后自行扩展学习。

​​​​​​​4.3.3break和continue

        break[n]可以指定跳出几层循环;

        continue跳过本次循环,但不会跳出循环。

        即break跳出,continue跳过。

​​​​​​​4.4位置参数和特殊变量

        有很多特殊变量是被Shell自动赋值的,我们已经遇到了$?和$1。其他常用的位置参数和特殊变量在这里总结一下:

  • $0                    相当于C语言main函数的argv[0]

  • $1、$2... 这些称为位置参数(Positional Parameter),相当于C语言main函数的argv[1]、argv[2]...

  • $#                    相当于C语言main函数的argc - 1,注意这里的#后面不表示注释

  • $@                  表示参数列表"$1" "$2" ...,例如可以用在for循环中的in后面。

  • $*                    表示参数列表"$1" "$2" ...,同上

  • $?                    上一条命令的Exit Status

  • $$                    当前进程号

        位置参数可以用shift命令左移。比如shift 3表示原来的$4现在变成$1,原来的$5现在变成$2等等,原来的$1、$2、$3丢弃,$0不移动。不带参数的shift命令相当于shift 1。例如:

#! /bin/sh

echo "The program $0 is now running"

echo "The first parameter is $1"

echo "The second parameter is $2"

echo "The parameter list is $@"

shift

echo "The first parameter is $1"

echo "The second parameter is $2"

echo "The parameter list is $@"

4.5输入输出

4.5.1echo

显示文本行或变量,或者把字符串输入到文件。

echo [option] string

-e 解析转义字符

-n 不回车换行。默认情况echo回显的内容后面跟一个回车换行。

echo "hello\n\n"

echo -e "hello\n\n"

echo "hello"

echo -n "hello"

​​​​​​​4.5.2管道

可以通过 | 把一个命令的输出传递给另一个命令做输入。

实际上是将前面进程的标准输出重定向到后边进程的标准输入。

cat myfile | more

ls -l | grep "myfile"

df -k | awk '{print $1}' | grep -v "文件系统"

df -k 查看磁盘空间,找到第一列,去除“文件系统”,并输出

​​​​​​​4.5.3tee

tee命令把结果输出到标准输出,另一个副本输出到相应文件。

df -k | awk '{print $1}' | grep -v "文件系统" | tee a.txt

tee -a a.txt表示追加操作。

df -k | awk '{print $1}' | grep -v "文件系统" | tee -a a.txt

​​​​​​​文件重定向

cmd > file                                 把标准输出重定向到新文件中

cmd >> file                      追加

cmd > file 2>&1              标准出错也重定向到1所指向的file里

cmd >> file 2>&1

cmd < file1 > file2 输入输出都定向到文件里

cmd < &fd                                把文件描述符fd作为标准输入

cmd > &fd                                把文件描述符fd作为标准输出

cmd < &-                                  关闭标准输入

​​​​​​​4.6函数

        和C语言类似,Shell中也有函数的概念,但是函数定义中没有返回值也没有参数列表。例如:

! /bin/sh

foo(){ echo "Function foo is called";}

echo "-=start=-"

foo

echo "-=end=-"

        注意函数体的左花括号 { 和后面的命令之间必须有空格或换行,如果将最后一条命令和右花括号 } 写在同一行,命令末尾必须有分号;。但,不建议将函数定义写至一行上,不利于脚本阅读。

        在定义foo()函数时并不执行函数体中的命令,就像定义变量一样,只是给foo这个名一个定义,到后面调用foo函数的时候(注意Shell中的函数调用不写括号)才执行函数体中的命令。Shell脚本中的函数必须先定义后调用,一般把函数定义语句写在脚本的前面,把函数调用和其它命令写在脚本的最后(类似C语言中的main函数,这才是整个脚本实际开始执行命令的地方)。

        Shell函数没有参数列表并不表示不能传参数,事实上,函数就像是迷你脚本,调用函数时可以传任意个参数,在函数内同样是用$0、$1、$2等变量来提取参数,函数中的位置参数相当于函数的局部变量,改变这些变量并不会影响函数外面的$0、$1、$2等变量。函数中可以用return命令返回,如果return后面跟一个数字则表示函数的Exit Status。

        下面这个脚本可以一次创建多个目录,各目录名通过命令行参数传入,脚本逐个测试各目录是否存在,如果目录不存在,首先打印信息然后试着创建该目录。

#! /bin/sh

is_directory()

{

        DIR_NAME=$1

        if [ ! -d $DIR_NAME ]; then

                 return 1

        else

                 return 0

        fi

}

for DIR in "$@"; do

        if is_directory "$DIR"

        then :

        else

                 echo "$DIR doesn't exist. Creating it now..."

                 mkdir $DIR > /dev/null 2>&1

                 if [ $? -ne 0 ]; then

                         echo "Cannot create directory $DIR"

                         exit 1

                 fi

        fi

done

注意:is_directory()返回0表示真返回1表示假。

​​​​​​​五、Shell脚本调试方法

Shell提供了一些用于调试脚本的选项,如:

-n             读一遍脚本中的命令但不执行,用于检查脚本中的语法错误。

-v              一边执行脚本,一边将执行过的脚本命令打印到标准错误输出。

-x              提供跟踪执行信息,将执行的每一条命令和结果依次打印出来。

这些选项有三种常见的使用方法:

        1)在命令行提供参数。如:

$ sh -x ./script.sh

        2)在脚本开头提供参数。如:

#! /bin/sh -x

        3)在脚本中用set命令启用或禁用参数。如:

#! /bin/sh

if [ -z "$1" ]; then

        set -x

        echo "ERROR: Insufficient Args."

        exit 1

        set +x

fi

        set -x和set +x分别表示启用和禁用-x参数,这样可以只对脚本中的某一段进行跟踪调试。

​​​​​​​六、正则表达式

        以前我们用grep在一个文件中找出包含某些字符串的行,比如在头文件中找出一个宏定义。其实grep还可以找出符合某个模式(Pattern)的一类字符串。

        例如找出所有符合xxxxx@xxxx.xxx模式的字符串(也就是email地址),要求x字符可以是字母、数字、下划线、小数点或减号,email地址的每一部分可以有一个或多个x字符,例如abc.d@ef.com、1_2@987-6.54,当然符合这个模式的不全是合法的email地址,但至少可以做一次初步筛选,筛掉a.b、c@d等肯定不是email地址的字符串。

        再比如,找出所有符合yyy.yyy.yyy.yyy模式的字符串(也就是IP地址),要求y是0-9的数字,IP地址的每一部分可以有1-3个y字符。

        如果要用grep查找一个模式,如何表示这个模式,这一类字符串,而不是一个特定的字符串呢?从这两个简单的例子可以看出,要表示一个模式至少应该包含以下信息:

        字符类(Character Class):如上例的x和y,它们在模式中表示一个字符,但是取值范围是一类字符中的任意一个。

        数量限定符(Quantifier): 邮件地址的每一部分可以有一个或多个x字符,IP地址的每一部分可以有1-3个y字符。

        各种字符类以及普通字符之间的位置关系:例如邮件地址分三部分,用普通字符@和.隔开,IP地址分四部分,用.隔开,每一部分都可以用字符类和数量限定符描述。为了表示位置关系,还有位置限定符(Anchor)的概念,将在下面介绍。

        规定一些特殊语法表示字符类、数量限定符和位置关系,然后用这些特殊语法和普通字符一起表示一个模式,这就是正则表达式(Regular Expression)。例如email地址的正则表达式可以写成[a-zA-Z0-9.-]+@[a-zA-Z0-9.-]+.[a-zA-Z0-9_.-]+,IP地址的正则表达式可以写成[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}。我们先看看正则表达式在grep中怎么用。例如有这样一个文本文件testfile:

192.168.1.1

1234.234.04.5678

123.4234.045.678

abcde

$ egrep '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' testfile

192.168.1.1

1234.234.04.5678

        egrep相当于grep -E,表示采用Extended正则表达式语法。grep的正则表达式有Basic和Extended两种规范,它们之间的区别下一节再解释。另外还有fgrep命令,相当于grep -F,表示只搜索固定字符串而不搜索正则表达式模式,不会按正则表达式的语法解释后面的参数。

        注意正则表达式参数用单引号括起来了,因为正则表达式中用到的很多特殊字符在Shell中也有特殊含义(例如),只有用单引号括起来才能保证这些字符原封不动地传给grep命令,而不会被Shell解释掉。

        192.168.1.1符合上述模式,由三个.隔开的四段组成,每段都是1到3个数字,所以这一行被找出来了,可为什么1234.234.04.5678也被找出来了呢?因为grep找的是包含某一模式的行,这一行包含一个符合模式的字符串234.234.04.567。相反,123.4234.045.678这一行不包含符合模式的字符串,所以不会被找出来。

        grep是一种查找过滤工具,正则表达式在grep中用来查找符合模式的字符串。其实正则表达式还有一个重要的应用是验证用户输入是否合法,例如用户通过网页表单提交自己的email地址,就需要用程序验证一下是不是合法的email地址,这个工作可以在网页的Javascript中做,也可以在网站后台的程序中做,例如PHP、Perl、Python、Ruby、Java或C,所有这些语言都支持正则表达式,可以说,目前不支持正则表达式的编程语言实在很少见。除了编程语言之外,很多UNIX命令和工具也都支持正则表达式,例如grep、vi、sed、awk、emacs等等。“正则表达式”就像“变量”一样,它是一个广泛的概念,而不是某一种工具或编程语言的特性。

​​​​​​​6.1基本语法

        我们知道C的变量和Shell脚本变量的定义和使用方法很不相同,表达能力也不相同,C的变量有各种类型,而Shell脚本变量都是字符串。同样道理,各种工具和编程语言所使用的正则表达式规范的语法并不相同,表达能力也各不相同,有的正则表达式规范引入很多扩展,能表达更复杂的模式,但各种正则表达式规范的基本概念都是相通的。本节介绍egrep(1)所使用的正则表达式,它大致上符合POSIX正则表达式规范,详见regex(7)(看这个man page对你的英文绝对是很好的锻炼)。希望读者仿照上一节的例子,一边学习语法,一边用egrep命令做实验。

​​​​​​​6.1.1字符类

​​​​​​​6.1.2数量限定符

再次注意grep找的是包含某一模式的行,而不是完全匹配某一模式的行。

例如有如下文本:

aaabc

aad

efg

查找a*这个模式的结果。会发现,三行都被找了出来。

$ egrep 'a*' testfile

aaabc

aad

efg

        a匹配0个或多个a,而第三行包含0个a,所以也包含了这一模式。单独用a这样的正则表达式做查找没什么意义,一般是把a*作为正则表达式的一部分来用。

​​​​​​​6.1.3位置限定符

位置限定符可以帮助grep更准确地查找。

例如上一节我们用[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}查找IP地址,找到这两行 

z192.168.1.1

1234.234.04.5678

        如果用^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$查找,就可以把1234.234.04.5678这一行过滤掉了。

​​​​​​​6.1.4其它特殊字符

​​​​​​​​​​​​​​6.2Basic正则和Extended正则区别

        以上介绍的是grep正则表达式的Extended规范,Basic规范也有这些语法,只是字符?+{}|()应解释为普通字符,要表示上述特殊含义则需要加\转义。如果用grep而不是egrep,并且不加-E参数,则应该遵照Basic规范来写正则表达式。​​​​​​​

总结:本文篇幅较大,有疑问的可以评论区交流。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/616333.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【SLAM】Kimera-Multi (IEEE-TRO2022 年最佳论文傅京孙)

Kimera-Multi: Robust, Distributed, Dense Metric-Semantic SLAM for Multi-Robot Systems 0 摘要1. 引言2. RELATED WORK3. SYSTEM OVERVIEW4. DISTRIBUTED LOOP CLOSURE DETECTION[4.X Kimera-Multi相关补充](https://github.com/DEARsunshine/Kimera)5. EXPERIMENTS6. CONC…

推箱子-第14届蓝桥杯国赛Scratch真题初中级组第3题

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第145讲。 推箱子&#xff0c;本题是2023年5月28日上午举行的第14届蓝桥杯国赛Scratch图形化编程初中级组真题第3题&am…

苹果iOS证书制作教程

众所周知&#xff0c;如果你需要上架苹果APP就必须要苹果iOS证书进行APP签名&#xff0c;否则苹果手机将无法安装你开发的APP&#xff0c;废话不多说&#xff0c;直接上教程。 第一步&#xff0c;注册账号 准备appleid必须开通双重认证&#xff0c;如果注册个人开发者直接下载d…

深度学习基础知识-tf.keras实例: 加州房价预测

参考书籍&#xff1a;《Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow, 2nd Edition (Aurelien Geron [Gron, Aurlien])》 代码有修改&#xff0c;已测通。 简单顺序结构 这次得数据集比之前得简单&#xff0c;只包含数字型特征&#xff0c;没有ocean…

leetcode98. 验证二叉搜索树(java)

验证二叉搜索树 leetcode98. 验证二叉搜索树题目描述 递归法解题思路代码演示 中序遍历解法解题思路代码演示 二叉树专题 leetcode98. 验证二叉搜索树 leetcode 98.验证二叉搜索树 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/…

Linux开发中的辅助工具

文章目录 前言一、add2line二、strip三、ar四、nm五、objdump六、size七、strings总结 前言 本篇文章我们来介绍一些Linux开发中的辅助工具&#xff0c;有了这些辅助工具将会让我们的开发变的更加轻松。 一、add2line addr2line是一个GNU调试工具&#xff0c;用于将程序计数…

priority_queue的模拟实现和仿函数

priority_queue模拟 首先查看源代码&#xff0c;源代码就在queue剩下的部分中 push_heap是STL库中的堆算法&#xff0c;STL库中包装有支持堆的算法&#xff0c;在algorithm.h中&#xff1a; 只要不断用堆的形式插入数据&#xff0c;就会形成堆。 priority_queue模拟——初版 pr…

自定义组件中,使用onLoad,onShow生命周期失效问题

的解决方法 自定义组件中&#xff0c;使用onLoad,onShow生命周期失效问题 自定义组件中&#xff0c;使用onLoad,onShow生命周期失效问题 官方文档可查阅到&#xff1a; 页面生命周期仅在page中的vue页面有效&#xff0c;而单独封装的组件中【页面周期无效】&#xff0c;但是Vu…

Pytorch入门(四)使用VGG16网络训练CIFAR10数据集

本文使用PytorchVGG16官方CIFAR10数据集完成图像分类。识别效果如下&#xff1a; 文章目录 一、VGG16 神经网络结构二、VGG16 模型训练三、预测CIFAR10中的是个类别 一、VGG16 神经网络结构 VGG&#xff0c;又叫VGG-16&#xff0c;顾名思义就是有16层&#xff0c;包括13个卷…

地震勘探基础(十)之地震速度关系

地震速度 地震勘探中引入了多种速度的概念&#xff0c;如下图所示。 层速度、平均速度和均方根速度之间的关系 层速度指的是某一套地层垂向上&#xff0c;由于地质条件相对稳定&#xff0c;地层顶底厚度比上地震波的传播时间为层速度&#xff0c;用 v n v_n vn​ 表示。 如下…

一文看懂软件架构4+1视图

目录 一、概述 二、各视图详解 1. 场景视图 2. 逻辑视图 3. 开发视图 4. 处理视图 5. 物理视图 葵花宝典&#xff1a;一看就懂的理解方式 一、概述 41视图包括&#xff1a; 场景视图&#xff08;也叫用例视图&#xff09;&#xff1a;黑盒视图。从外部视角&#xff…

chatgpt赋能python:Python如何分段数据的平均数

Python如何分段数据的平均数 Python是一门极其流行的编程语言&#xff0c;广泛应用于数据分析与科学计算领域。在数据分析中&#xff0c;计算各个数据段的平均数是一项常见的任务。本文将介绍如何使用Python分段计算数据的平均数&#xff0c;以及如何优化这一过程以使速度更快…

Linux中的lrzsz

一、介绍 lrzsz是一款在Linux里可代替ftp上传和下载的程序,也就是一款软件。它是开发者常用的一款工具,这个工具用于windows机器和远端的Linux机器通过XShell传输文件。 二、lrzsz的安装 在安装之前,我们可以使用下述命令先查看yum仓库中是否存在我们要安装的软件: yum…

CentOS7使用Docker快速安装Davinci

环境信息 操作系统&#xff1a;CentOS7Docker : 23.0.6 &#xff08;已配置阿里云镜像加速&#xff09; 安装步骤 安装docker-compose-plugin 官方的例子使用的是docker-compose&#xff0c;但是由于yum能够安装的最新斑斑是1.x,而且官方的docker-compose要求最低版本为2.2以…

首个区块链技术领域国家标准出台 ,中创助力打造区块链技术和应用创新高地

区块链作为数字中国的重要技术底座&#xff0c;正在深刻改变着我国社会生产方式。何谓区块链&#xff0c;对大众来说&#xff0c;也许尚陌生&#xff0c;殊不知&#xff0c;这一产业已稳稳起跑在我国高质量发展的“赛道”上。 近日&#xff0c;《区块链和分布式记账技术参考架…

【JavaScript】超全基础万字大总结

目录 一、初识 JavaScript 1.1 JavaScript 是什么&#xff1f; 1.2 发展历史 1.3 JavaScript 和 HTML 和 CSS 之间的关系 1.4 JavaScript 运行过程 1.5 JavaScript 的组成 二、前置知识 2.1 第一个程序 2.2 JavaScript 的书写形式 2.3 输入输出 三、语法概览 3.1 变…

Linux(CentOS 7) 安装 Mysql8 、Java 以及 mycat2 详细流程

目录 一、Mysql8 安装 1.下载mysql8 2. 解压Mysql 压缩包 3.重名命mysql 文件 4.创建data文件夹 储存文件 5.创建用户组以及用户 6.授权用户 将mysql文件夹的所有者和所有组都改为mysql 7.mysql初始化进入bin目录执行mysqld文件进行初始化 8.编辑my.cnf 9.添加mysqld…

有哪些虚拟化和容器化工具推荐? - 易智编译EaseEditing

以下是几个常用的虚拟化和容器化工具推荐&#xff1a; VMware vSphere&#xff1a; VMware vSphere 是一套完整的虚拟化平台&#xff0c;包括虚拟化服务器、虚拟化存储和虚拟化网络。 它提供了高性能的虚拟机管理和资源调度功能&#xff0c;适用于企业级的虚拟化部署。 Docke…

IT知识百科:什么是跨站脚本(XSS)攻击?

跨站脚本&#xff08;Cross-Site Scripting&#xff0c;XSS&#xff09;是一种常见的网络安全漏洞&#xff0c;攻击者利用该漏洞在受害者的网页中插入恶意脚本&#xff0c;从而能够获取用户的敏感信息、劫持会话或进行其他恶意活动。本文将详细介绍跨站脚本攻击的原理、类型、常…

vue props传值层级多,子级孙子级怎么修改传参

vue props传值层级多了&#xff0c;子级孙子级怎么修改传参 1.出现背景2.怎么在孙组件里改变传过来的值呢2.1这样改是不行的2.2可行的方法2.2.1 引用对象只改变单属性2.2.2 provide和inject 1.出现背景 本来自己写页面的话是直接全部写在一个vue文件里&#xff0c;一个vue文件…