AKever

导航

Linux shell 语法基础

Linux shell 语法基础

零:运行脚本

$ /bin/sh hello$ chmod +x hello

==============================

变量:字符串、数字、环境和参数

条件:shell中的布尔值

程序控制:if\elif\for\while\until\case

命令列表

函数

shell内置命令

获取命令的执行结果

here文档

==============================

一. 变量

举个栗子1:

salutation=Hello
echo $salutation
输出:Hello

salutation="Yes Dear"
echo $salution
输出:Yes Dear

salutation=7+5
echo $salutation
输出:7+5

warn:如果字符串里包含空格,就必须用引号把它们括起来。此外,等号两边不能有空格。

举个栗子2:read命令-这命令需要一个参数,即准备读取输入的数据

$ read salutation
输入:Wie geht's
$ echo $salutation
输出:Wie geht's

1.引号的使用

一般情况下,脚本文件中的参数以空白字符分隔(如,一个空格、一个制表符或一个换行符)。就必须给参数加上引号

#!/bin/sh

myvar="Hi there"                    #赋值

echo $myvar                         # Hi there --字符串
echo "$myvar"                       # Hi there --字符串
echo '$myvar'                       # $myvar --字符串
echo \$myvar                        # $myvar --字符串

echo Enter some text               #Enter some text --字符串
read myvar                          #输入 Hello

echo '$myvar' now equals $myvar
#输出:'$myvar' now equals Hello
exit 0                                                    

 

二. 环境变量

环境变量一般使用大写字母做名字,以下是一些主要的变量

$HOME     当前用户的家目录
$PATH      以冒号分隔的用来搜索命令的目录列表
$PS1        命令提示符,通常是$字符,但在bash中,可以使用一些更复杂的:字符串:[\u@\h \w]$就是一个流行的默认值,它给出用户名、机器名和当前目录名,包括一个$提示符
$PS2        二级提示符,用来提示后续的输入,通常是>字符
$IFS        输入域分隔符。当shell读取输入时,他给出用来分隔单词的一组字符,它们通        常是空格、制表符和换行符
$0           shell脚本的名字
$#          传递给脚本的参数个数
$$          shell脚本的进程号,脚本程序通常会用它来生成一个唯一的临时文件,如 /tmp/tmpfile_$$

 

三. 参数变量

如果脚本程序在调用时带有参数,一些额外的变量就会被创建。即使没有传递任何参数,环境变量$#也依然存在,只不过它的值是0罢了。

$1, $2, ...        脚本程序的参数
$*                   在一个变量中列出所有的参数,各个参数之间用环境变量IFS中的第一个字符串分隔开。如果IFS被修改了,那么$*将命令行分隔为参数的方式就将随之改变
$@                  它是$*的一种精巧的辩题,它不适用IFS环境变量,所以即使IFS为空,参数也不会挤在一起

举个栗子1:

$ IFS=''
$ set foo bar bam
$ echo "$@"
foo bar bam
$ echo "$*"
foobarbam
$ unset IFS
$ echo "$*"
foo bar bam

如所见:双引号里面的$@把各个参数扩展为彼此分开的域,而不受IFS值影响。一般来说,如果你想访问脚本程序的参数,使用$@是明智的选择。除了echo外,还可以使用read命令来读取它们

 

举个栗子2:

#!/bin/sh

salutation="Hello"
echo $salutaion
echo "The program $0 is now running"
echo "The second parameter was $2"
echo "The first parameter was $1"
echo "The first parameter list was $*"
echo "The user's home directory is $HOME"

echo "Please enter a new greeting"
read salutation

echo $salutation
echo "The script is now complete"

exit 0

说明:chmod +x try_var命令把他设置为可执行

运行:

$ ./try_var foo bar baz
Hello
The program ./try_var is now running
The second parameter was bar
The first parameter was foo
The parameter list was foo bar baz
The user's home directory is /home/rick
Please enter a new greeting
Sire
Sire
The script is now complete
$

 

四. 条件

一个shell脚本能够对任何可以从命令杭商调用的命令的退出码进行测试,其中也包括你自己编写的脚本程序。这也是为什么要在自己写的脚本程序的结尾包括一条返回值的exit命令的重要原因。

test 或 [ 命令

1.举个栗子

if test -f fred.c
then
...
fi

if [-f fred.c]
then
...
end

test命令的退出码(表明条件是否被满足)决定是否需要执行后面的条件代码。

warn: 你必须在[符号和被检查的条件之间留出空格-和test一样。

    如果和then在同一行,需要用分号吧test语句和then分隔开,如下

if [ -f fred.c ]; then
...
fi

2.test条件类型

 test条件类型可分为3类:字符串比较、算术比较和与文件有关的条件测试。

2.1字符串比较

string1 = string2 #两个字符串相同则结果为真
string1 != string2 #如果不同为真
-n string              #如果字符串不为空则结果为真
-z string              #如果字符串为null, 则结果为真

2.2算术比较

expression1 -eq expression2    #如果两个表达式相等则真
expression1 -ne expression2    #如果两个表达式不等则真
expression1 -gt expression2     #如果expression1大于expression2则真
expression1 -ge expression2    #如果expression1大于等于expession2则真
expression1 -lt expression2      #如果expression1小于expression2则真
expression1 -le expression2      #如果expression1小于等于expression2则真
!expression                                #如果表达式为假则真,反之亦然

2.3文件条件测试

-d file     #如果文件是一个目录则真
-e file     #如果文件存在则真。注意: -e选项不可以移植,通常使用-f
-f file      #如果文件是一个普通文件则真
-g file     #如果文件的set-group-id位被设置则结果为真
-r file      #如果文件可读则真
-s file     #如果文件的大小不为0则结果为真
-u file     #如果文件set-user-id位被设置则结果为真
-w file    #如果文件可写则真
-x file     #如果文件可执行则真
-d file     #如果文件是一个目录则真
-e file     #如果文件存在则真。注意: -e选项不可以移植,通常使用-f
-f file      #如果文件是一个普通文件则真
-g file     #如果文件的set-group-id位被设置则结果为真
-r file      #如果文件可读则真
-s file     #如果文件的大小不为0则结果为真
-u file     #如果文件set-user-id位被设置则结果为真
-w file    #如果文件可写则真
-x file     #如果文件可执行则真

说明:set-user-id(get-uid)位授予了程序其拥有者的访问权限而不是使用者,set-group-id(set-gid)位授予了程序其所在组的访问权限。这个两个特殊位是用过chmod命令的选项s和g设置的。set-gid和set-uid标识对shell脚本程序不起作用,只对二进制文件有用。

举个栗子1:

#!/bin/sh

if [ -f /bin/bash ]
then
    echo "file /bin/bash exists"
fi

if [ -d /bin/bash ]
then
    echo "/bin/bash is a directory"
else
    echo "/bin/bash is NOT a directory"
fi

 

举个栗子2: if and elif

#!/bin/sh

echo "Is it morning? Please answer yes or no"
read timeof day

if [ "$timeofday" = "yes" ]; then
    echo "Good morning"
elif [ "$timeofday" = "no" ]; then   #"$timeofday"带双引号
    echo "Good afternoon"
else
  echo "Sorry, $timeofday not recognized. Enter yes or no"
  exit 1 fi exit
0

输出

Is it morning? Please answer yes or no
yes
Good morning
$

 3.for语句

3.1.格式

for variable in values
do
    statements
done

举个栗子1:

#!/bin/sh

for foo in bar fud 43
do
    echo $foo
done
exit 0

输出:

bar
fud
43

说明:如果你把第一行改为for foo in "bar fud 43", 输出就是这个字符串

    加引号就是告诉shell吧引号之间的内容看做一个字符串,这个保留空格最好的办法

 3.2.使用通配符扩展的for循环

把*扩展为当前目录中所有文件的名字,然后一直作为for循环中的变量¥file使用

举个栗子:

#!/bin/sh

for file in $(ls f*.sh); do
    lpr $file
done
exit 0

说明:这个例子演示了$(command)语法的用法,for命令的参数表来自在$()中的命令的输出结果。shell扩展f*.sh给出所有匹配此模式的文件的名字。

 4.while语句

在默认的情况下,所有的shell变量值都被认为是字符串,所以for循环特别适合对一系列字符串进行循环处理。

4.1格式

while condition do
    statements
end

4.2举个栗子:

#!/bin/sh

echo "Enter password"
read trythis

while [ "$trythis" != "secret" ]; do
    echo "Sorry, try again"
    read trythis
done
exit 0

输出:

Enter password
password
Sorry, try again
secret
$

5.until语句

5.1格式

until condition
do
    statements
done

说明:与while循环相似,条件测试相反

#!/bin/bash

util who | grep "$1" > /dev/null
do 
    sleep 60
done

#now ring the bell and announce the expected use.

echo -e '\a'
echo "**** $1 has just logged in ****"

exit 0

6.case语句

case结构格式

case variable in
    pattern [ | pattern] ...) statements;;
    pattern [ | pattern] ...) statements;;
    ...
esac

说明:每个模式行都与双分号(;;)结尾,因为你可以子啊前后模式之间放置多条语句。所以需要使用一个双分号来标识前一个语句的结束和后一个模式的开始。

6.1举个栗子:用户输入

#!/bin/sh

echo "Is it morning? Please answer yes or no"
read timeofday

case "$timeofday" in
    yes)    echo "Good Morning";;
    no )    echo "Good Afternoon";;
    y   )    echo "Good Morning";;
    n   )    echo "Good Afternong";;
    *   )    echo "Sorry, answer not recognized";;
esac

exit 0

6.2举个栗子2:合并匹配模式

#!/bin/sh

echo "Is it morning? Please answer yes or no"
read timeofday

case "$timeofday" in
    yes | y | Yes | YES )    echo "Good Morning";;
    n* | N* )                echo "Good Afternoon";;
    * )                      echo "Sorry, answer not recognized";;
esac

exit 0

说明:每个case条目中都使用了多个字符串,case将对每个条目中的多个不同字符串进行测试,以决定是否需要执行相应的语句。这使得脚本程序不仅长变短,而且实际上也更容易阅读。这个脚本同事显示了*通配符的用法,虽然这样有可能匹配意料之外的模式。例如:如果用户输入never,他就会匹配n*并显示出Good Afternong,而这不是我们希望的,另外需要注意的是*通配符扩展在引号中不起作用

 6.3举个栗子3:执行多条语句

#!/bin/sh

echo "Is it morning? Please answer yes or no"
read timeofday

case "$timeofday" in
    yes | y | Yes |YES )
        echo "Good morning"    
        echo "Up bright and early this morning"
        ;;
    [nN]*)
        echo "Good Afternoon"
        ;;
    *)
        echo "Sorry, answer not recognized"
        echo "Please answer yes or no"    
        exit 1
        ;;
esac

exit 0

说明:最后,为了让脚本程序具备可重用姓,需要在使用默认模式下给出另外一个退出码。

warn: esac前面的(;;)是可选的

===========================================================

为了让case的匹配功能更强大,你可以使用如下模式

[yY] | [Yy][Ee][Ss] )

说明:这限制了允许出现的字母,但它同时也允许多种多样答案并且提供了比*通配符更多的控制

 

7.命令列表

有时,可能想要将几条命令连接成一个序列,例如,你可能想子啊执行某个语句之前同时满足好几个不同的条件,如下所示:

if [ -f this_file ]; then
    if [ -f that_file ]; then
         if [ -f the_other_file ]; then
            echo "All files present, and correct"
        fi
    fi
fi

或者你可能希望至少在一个序列条件中有一个为真,像下面这样:

if [ -f this_file ]; then
    foo="True"
elif [ -f that_file ]; then
    foo="True"
elif [ -f the_other_file ]; then
    foo="True"
else
    foo="False"
fi
if [ "$foo" = "True" ]; then
    echo "One fo the files exist"
fi

以上使用多个if来实现,笨拙,请勿模仿 ... ...

7.1 AND列表

格式

statement1 && statement2 && statement3 && ...

说明:从左边开始,一个命令返回true, 右边的下一个命令才能执行。直到false或完毕。&&的作用是检测前一条命令的返回值。

举个栗子1:

#!/bin/sh

touch file_one
rm -f file_two

if [ -f file_one ] && echo "hello" && [ -f file_two ] && echo " there"
then
    echo "in if"
else
    echo "in else"
fi

exit 0

输出:

hello
in else

 7.2 OR列表

格式

statemenst1 || statement2 || statement3

不多说了,栗子摆出:

#!/bin/sh

rm -f file_one

if [ -f file_one ] || echo "hello" || echo " there"
then
    echo "in if"
else
    echo "in else"
fi

exit 0

输出:

hello
in if

 

8.语句块

如果在某些只允许使用单个语句的地方(比如AND或OR列表中)使用多条语句,可以把他们用花括号{}构造一个语句块。如下

get_confirm && {
    grep -v "$cdcatnum" $tracks_file > $temp_file
    cat $temp_file > $tracks_file
    echo 
    add_record_tracks
}

 

五.函数

1.1格式

function_name() {
    statements
}

举个栗子:

#!/bin/sh
foo() {
    echo "Function foo is executing"
}

echo "script starting"
foo 
echo "script ended"

exit 0

输出:

script starting
Funciton foo is executing
script ending

 

1.2返回值

可通过return返回数字值

返回字符串值的常用方法是让函数将字符串保存在一个变量中,该变量在函数结束后被使用,此外,还可以echo一个字符串并捕获其结果

格式:

foo() { echo JAY; }
...
result="$(foo)"

说明:可以使用local关键字在shell函数中声明局部变量。

举个栗子1:

#!/bin/sh
sample_text="global variable"

foo() {
    local sample_text="local variable"
    echo "Function foo is executing"
    echo $sample_text
}

echo "script starting"
echo $sample_text

foo

echo "script ended"
echo $sample_text

exit 0

说明:如果在函数中没有使用return命令指定一个返回值,函数返回的就是执行的最后一条命令的退出码。

1.3.参数的传递

举个栗子:yes_or_no

函数部分--

#!/bin/sh

yes_or_no() {
    echo "Is your name $* ?"
    while true
    do 
        echo -n "Enter yes or no: "
        read x
        case "$x" in
            y | yes ) return 0;;
            n | no  ) return 1;;
            * )         echo "Answer yes or no"
        esac
    done
}

主程序部分 --

echo "Original parameters are $*"

if yes_or_no "$1"
then
    echo "Hi $1, nice name"
else
    echo "Never mind"
fi
exit 0

输出:

$ ./my_name Rick Neil
Original parameters are Rick Neil
Is your name Rick ?
Enter yes or no: yes
Hi Rick, nice name
$

说明:在if中,脚本程序执行到yes_or_no是,先把$1替换成脚本第一个参数Rick, 再把他作为参数传递给这个函数。函数将这些参数(他们现在被保存在$1\$2等位置参数中)并向调用者返回一个值。if结构在根据这个返回值去执行相应的语句。

 

六. 命令 

两类命令:一类是可以在命令提示符中执行的"普通"命令-外部命令(external command).

       一类是"内部命令",在shell内部实现,不能作为外部程序被调用。内部命令执行效率高。

记住:除了这里介绍的内置命令外,许多可以子啊命令提示符下执行的合法命令也可以在脚本程序中使用。

1.break命令

#!/bin/sh

rm -rf fred*
echo > fred1
echo > fred2
mkdir fred3
echo > fred4

for file in fred*
do
    if [ -d *$file* ]; then
        break;
    fi
done

echo first directory starting fred was $file
rm -rf fred*
exit 0

输出:(验证)

first directory starting fred was fred3

 

2. :命令

冒号(:)命令是一个空命令。偶尔会被用于简化条件逻辑,相当于true的一个别名。由于它是内置命令,所以它运行的比true快,但输出可读性较差。

可能你会看到将他用作while循环的条件:while:实现一个无限循环, 代替常见的while true。

:结构也会被用在变量的条件中设置,例如:

: ${var:=value}

说明:如果没有:,shell将试图把$var当作一条命令来处理

2.1注释

#!/bin/sh

rm -f fred
if [ -f fred ]; then
    :
else
    echo file fred did not exist 
fi
exit 0

说明:在旧的shell脚本中,会看到冒号被用在一行的开头表示注释,现在用#(效率较高)

3.continue命令

类似C语言中的同名语句,子啊for\while或until循环调到一下次继续执行。

不赘述 省略一万字 ...

3.w continue可带个参数,表示希望继续执行的循环的嵌套层数(用到极少,不好理解维护)

for x in 1 2 3
do
    echo before $x
    continue 1
    echo after $x
done

输出:

before 1
before 2
before 3

 

4. .命令

点(.)命令用于当前shell中的执行命令:

. ./shell_script

解析:通常,当一个脚本执行一条外部命令或脚本程序时,它会创建一个新的环境(一个子shell),命令将在这个新环境中执行,在命令执行完毕后,这个环境被丢弃,留下退出码返回给父shell,但外部的source命令和点命令(这两个命令差不多是同义词)在执行脚本程序中列出的命令时,使用的是调用该脚本程序的同一个shell。

因为在默认情况下,shell脚本程序会在一个新创建的环境中执行的,所以脚本程序对环境变量所做的任何修改都会丢失。而点命令允许执行的脚本程序改名当前环境。当你要把一个脚本当做"包裹器"来为后续执行的一些其他命令设置环境是,这个命令通常就很有用。例如,如果你正同时参与几个不同的项目,你就可能会遇到需要使用不同的参数来调用命令的情况,比如说调用一个老板本的编译器来维护一个旧程序。

在shell脚本程序中,点命令的作用有点类似C或C++语言的#include指令。尽管它并没有从字面意义上包含脚本,但它的确是在当前上下文中执行命令,所以你可以使用它将变量和上述定义结合进脚本程序。

4.1假设你有两个包含环境设置的文件,他们分别针对两个不同的开发环境。为了设置老的,经典命令的环境,你可以使用classic_set, 它的内容如下所示:

#!bin/sh

version=classic
PATH=/usr/local/old_bin:/usr/bin:/bin:.
PS1="classic> "

4.2对于新命令,使用文件latest_set:

#!/bin/sh

version=latest
PATH=/usr/local/new_bin:/usr/bin:/bin:.
PS1=" latest version> "

4.3你可以通过将这些脚本程序和点命令结合来设置环境,就像以下示例:

$ . ./classic_set
classic> echo $version
classic
classic> . /latest_set
latest version> echo $version
latest
latest version>

说明:这个脚本程序使用点命令执行,所以每个脚本都是在当前shell中执行。这使得脚本程序可以改变当前shell中的环境设置,即使在脚本执行结束后,这些改变仍然有效。

5.echo命令

虽然,X/Open建议现在shell中使用printf,但当代还是使用echo命令来输出结尾带有换行符的字符串。

5.1Linux去掉换行符的解决方法如下

echo -n "string to output"

5.2 echo -e确保启用反斜杠转义字符(如\c代表去掉换行符,\t代表制表符,\n代表回车)的解释。

echo -e "stirng to output\c"

 

6.eval命令(内置命令)

eval命令允许你对参数进行求值,通常不会以单独命令的形式存在。我们借用X/Open规范中的一个小例子来演示它的用法

foo=10
x=foo
y='$'$x
echo $y

输出:$foo ,而

foo=10
x=100
eval y='$'$x
echo $y

输出10。因此,eval命令有点像一个额外的$,它给出一个变量的值的值

eval命令十分有用,它允许代码被随时声场和运行。虽然它的确增加了脚本调试的复杂度,但它可以让你完成使用其他方法难以或者根本无法完成的事情。

 

7.trap命令(中断处理)

trap命令用于指定在接受到信号后采取的行为,我们将在本书后面的内容中详细介绍信号。trap命令的一种常见用途是在脚本程序被中断时完成清理任务。历史上, shell总是用数字来代表信号,但新的脚本程序应该使用信号的名字,他们定义在头文件 signel.h中。在使用信号时省略SIG前缀。可以在命令提示符下输入命令trap -l来查看信号编号以及其关联的名称。

trap命令两个参数,1.接收到指定信号时将要采取的行为,2.要处理的信号名。格式

trap command signal

remember:脚本程序通常是以从上到下的顺序解释执行的,所以必须在你想要保护的那部分代码之前指定trap命令

如果要重置某个信号的处理方式到其默认值,只需将command设置为-。如果要忽略某个信号,就把command设置为空字符串 ' '。一个不带参数的trap命令将列出当前设置的信号及其行动的清单。

举个型号列表:

HUP(1)        挂起,通常因终端掉线或用户退出而引发
INT(2)         中断,通常因按下Ctrl+C组合键而引发
QUIT(3)      退出,通常因按下Ctrl+\组合键而引发
ABRT(6)      中止,通常因某些严重的执行错误而引发
ALRM(14)    报警,通常用来处理超时
TERM(15)    终止,通常在系统关机时发送

举个栗子:

#!/bin/sh

trap 'rm -f /tmp/my_tmp_file_$$' INT
echo creating file /tmp/my_tmp_file_$$
date > /tmp/my_tmp_file_$$

echo "press interrupt (Ctrl-C) to interrupt ... "
while [ -f /tmp/my_tmp_file_$$ ]; do
    echo File exists
    sleep 1
done
echo The file no longer exists

trap INT
echo creating file /tmp/my_tmp_file_$$
date > /tmp/my_tmp_file_$$

echo "press interrupt (control-C) to interrupt ...."
while [ -f /tmp/my_tmp_file_$$ ]; do
    echo File exists
    sleep 1
done

echo we never get here
exit 0

运行:

creating file /tmp_my_tmp_file_141
press interrupt (CTRL-C) to interrupt ....
File exists
File exists
File exists
File exists
The file no longer exists
press interrupt (CTRL-C) to interrupt ....
File exists
File exists
File exists
File exists

 

n.后面好多命令

I just want to save the time! I shuold stop it ...

 

posted on 2016-07-05 23:28  AKever  阅读(448)  评论(0)    收藏  举报