shell notes&tips

tips

ps:下文中,我们使用$ 表示终端提示符表示输入命令的符号,- 表示多行命令的换行(多行命令不挤在一行以便美观),> 表示终端的输出。

右侧边有导航栏,可进行跳转


  • shell脚本通常以shebang起始,/bin/bash是Bash的解释器命令路径 #!/bin/bash

执行脚本

fork模式

我们所执行的任何程序,都是由父进程(parent process)所产生出来的一个子进程(child process),子进程在结束后,将返回到父进程去。此一现像在Linux系统中被称为 fork。当子进程被产生的时候,将会从父进程那里获得一定的资源分配、及(更重要的是)继承父进程的环境。

  • fork模式,脚本的执行方式有两种。

    • 将脚本名作为命令行参数(无须设置权限)

      $ bash myScript.sh

    • 授予脚本执行权限,将其变为可执行文件:

      1
      2
      $ chmod 755 myScript.sh
      $ ./myScript.sh.

      source模式

      source方式的特点是,在不另外创建子进程,而是在当前的的Shell环境中执行。

  • source模式,脚本的执行方式有两种。

    • source命令+文件路径

      $ source myScript.sh 或 source ./myScript.sh

    • . 命令

      $ . myScript.sh 或 . ./myScript.sh

exec模式

exec模式和source方式一样,不另外创建子进程,而是在当前的的Shell环境中执行脚本,但是执行完后会终止当前的shell进程,如果使用终端,可以看见执行exec后终端退出。

  • exec命令

    exec ./mytest.sh 或 exec myScript.sh


配置文件

你在命令行中输入的绝大部分命令都可以放置在一个特殊的文件中,留待登录或启动新的bash会话时执行。将函数定义、别名以及环境变量设置放置在这种特殊文件中,是一种定制shell的常用方法。

  • 当用户登录shell时,会执行下列文件:
    • /etc/profile
    • $HOME/.profile
    • $HOME/.bash_login
    • $HOME/.bash_profile

      注意,如果你是通过图形化登录管理器登入的话,是不会执行/etc/profile、$HOME/.profile和$HOME/.bash_profile这3个文件的。这是因为图形化窗口管理器并不会启动shell。当你打开终端窗口时才会创建shell,但这个shell也不是登录shell。

如果.bash_profile或.bash_login文件存在,则不会去读取.profile文件。

  • 交互式shell(如X11终端会话)或ssh执行单条命令(如ssh 192.168.1.1 ls /tmp)时,
    会读取并执行以下文件:
    • /etc/bash.bashrc
    • $HOME/.bashrc
  • 调用ssh登录会话
    ssh 192.168.1.100
    这会创建一个新的登录bash shell,该shell会读取并执行以下文件:
    • /etc/profile
    • /etc/bash.bashrc
    • $HOME/.profile
    • .bashrc_profile
  • 运行脚本
    1
    2
    3
    4
    //如果运行如下脚本:
    $> cat myscript.sh
    #!/bin/bash
    echo "Running"
    不会执行任何配置文件,除非定义了环境变量BASH_ENV:
    1
    2
    $> export BASH_ENV=~/.bashrc 
    $> ./myscript.sh

    变量

Shell变量大致可以分为3种类型:

  • 内部变量:系统提供,不用定义,不能修改,比如$#,$?,$*,$0等

  • 环境变量:系统提供,不用定义,可以修改,当前进程及其子进程中使用,比如PATH,PWD,SHELL等

  • 用户变量(本地变量):用户定义,可以修改,在当前进程使用,比如var=123等

    定义变量

    定义变量有如下几种形式

    • 不加符号的等号操作符赋值

    varName=value

    如果value不包含任何空白字符(例如空格),那么就不需要将其放入引号中,否则必须使用单引号或双引号。

    • 单引号的等号操作符赋值
      1
      2
      3
      4
      //单引号不扩展或解释任何变量和符号
      $ test='ps$?'
      $ echo $test
      > ps$?
    • 双引号的等号操作符赋值
      1
      2
      3
      4
      //双引号会扩展解释变量和符号,其中$?为上条命令执行的结果
      $ test="ps$?"
      $ echo $test
      > ps0
    • 反引号的等号操作符赋值
      1
      2
      3
      4
      //反引号(键盘上的~键),将内容命令的输出存入变量,该例中将ps命令的输出存入test
      $ test=`ps`
      $ echo $test
      PID TTY TIME CMD 2856 pts/0 00:00:00 bash 3234 pts/0 00:00:00 ps
    • 子shell的等号操作符赋值
      1
      2
      3
      4
      //使用$(),将开启子shell,或者说子进程执行内容命令,并将内容命令的输出存入变量,该例中将ps命令的输出存入test
      $ test=$(ps)
      $ echo $test
      PID TTY TIME CMD 2856 pts/0 00:00:00 bash 3234 pts/0 00:00:00 ps

      注意,var = value不同于var=value。两边没有空格的等号是赋值操作符,加上空格的等号表示的是等量关系测试。

    • export命令
      1
      2
      3
      4
      $ HTTP_PROXY=192.168.1.23:3128
      $ export HTTP_PROXY
      //export命令声明了将由子进程所继承的一个或多个变量。
      //这些变量被导出后,当前shell脚本所执行的任何应用程序都会获得这个变量。

      如果需要append变量,例如对PATH中添加一条新路径,可以使用如下命令:

      export PATH
      1
      2
      ### 常见的环境变量
      - SHELL:环境变量SHELL获知当前使用的是哪种shell

      $ echo $SHELL
      $ echo $0
      /bin/bash

      1
      2
      3
      4
      5
      - UID:环境变量UID中保存的是用户ID。root用户的UID是0。
      - PS1:当我们打开终端或是运行shell时,会看到类似于user@hostname:/home/$ 的提示字符串。不同的GNU/Linux发布版中的提示字符串及颜色各不相同。我们可以利用PS1环境变量来定义主提示字符串。
      - PATH:PATH环境变量通常保存了可用于搜索可执行文件的路径列表。`PATH=/usr/bin; /bin`这意味着只要shell执行应用程序(二进制文件或脚本)时,它就会首先查找/usr/bin,然后查找/bin。
      - LD_LIBRARY_PATH:LD_LIBRARY_PATH环境变量通常保存了可用于搜索库文件的路径列表。`LD_LIBRARY_PATH=/usr/lib; /lib`这意味着只要shell执行库文件时,它就会首先查找/usr/lib,然后查找/lib。
      - IFS:内部字段分隔符(internal field separator)。IFS环境变量保存了用于分隔的字符。它是当前shell环境使用的默认定界字符串。IFS的默认值为空白字符(换行符、制表符或者空格)。
      $ oldIFS=$IFS
    • IFS=, #IFS现在被设置为,
    • for item in $data;
    • do
    • echo Item: $item
    • done
    • IFS=$oldIFS

    Item: name
    Item: gender
    Item: rollno
    Item: location

    1
    2
    3
    - SHLVL:保存当前shell的层级
    ### 访问变量
    - 和编译型语言不同,大多数脚本语言不要求在创建变量之前声明其类型。用到什么类型就是什么类型。在变量名前面加上一个美元符号就可以访问到变量的值。也可以使用${var}。其分别如下:

    $ fruit=apple
    $ count=5
    $ echo “We have $count ${fruit}s”

    We have 5 apples
    //因为shell使用空白字符来分隔单词,
    //所以我们需要加上一对花括号来告诉shell这里的变量名是fruit,
    //而不是fruits。

    1
    2
    ### 获得字符串的长度
    - 可以用下面的方法获得变量值的长度:

    $ var=12345678901234567890
    $ echo $操作符

% %% # ## 操作符可以得到变量var删除特定的值后的结果:

假设我们定义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} :提取从第0个开始的连续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

    同样的:单一符号是最小匹配;吝啬匹配
    两个符号是最大匹配;贪婪匹配

    &&和||

  • shell 在执行某个命令的时候,会返回一个返回值,该返回值保存在 shell 变量 $? 中。当 $? == 0 时,表示执行成功;当 $? == 1 时(我认为是非0的数,返回值在0-255间),表示执行失败。
  • 有时候,下一条命令依赖前一条命令是否执行成功。如:在成功地执行一条命令之后再执行另一条命令,或者在一条命令执行失败后再执行另一条命令等。shell 提供了 && 和 || 来实现命令执行控制的功能,shell 将根据 && 或 || 前面命令的返回值来控制其后面命令的执行。
  • 无论是&&还是||,联合命令行都会尽量执行至成功为止。(这才有了短路的意义)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    command1 && command2 [&& command3 ...]
    //命令之间使用 && 连接,实现逻辑与的功能。
    //只有在 && 左边的命令返回真(命令返回值 $? == 0),&& 右边的命令才会被执行。
    //只要有一个命令返回假(命令返回值 $? == 1),后面的命令就不会被执行。

    command1 || command2 [|| command3 ...]
    //命令之间使用 || 连接,实现逻辑或的功能。
    //只有在 || 左边的命令返回假(命令返回值 $? == 1),|| 右边的命令才会被执行。这和 c 语言中的逻辑或语法功能相同,即实现短路逻辑或操作。
    //只要有一个命令返回真(命令返回值 $? == 0),后面的命令就不会被执行。 –直到返回真的地方停止执行。

    管道符 |

Unix shell脚本最棒的特性之一就是可以轻松地将多个命令组合起来生成输出。一个命令的输出可以作为另一个命令的输入,而这个命令的输出又会传递至下一个命令,以此类推。

1
2
//这里我们组合了3个命令。cmd1的输出传递给cmd2,cmd2的输出传递给cmd3,最终的输出 12(来自cmd3)会出现在显示器中或被导入某个文件。
$ cmd1 | cmd2 | cmd3

shell代码执行顺序

重定向执行顺序

  • 先读取输入重定向符<后的内容做为输入,如果一条命令有多个<,会读取最后一个<后的内容
    输入重定向符<放在命令前后都可以,例如【< /etc/hosts cat】相当于【cat /etc/hosts】

  • 执行命令

  • 如果有>或>>会将结果进行重定向,如果输出重定向多个文件,只会将内容重定向到最后一个文件
    例如 cat /etc/hosts > test1.txt >test2.txt,只有test2.txt会出现内容,test1.txt内容是空的
    输出重定向>和>>的位置放在哪里都可以,例如【> test.txt cat /etc/hosts】,表示将/etc/hosts的内容输入到test.txt中

管道符执行顺序

command1 | command2
命令1必须要有输出,且是正确的。命令2才会执行。命令1的输出作为命令2的输入


数学运算-let、(( ))和[]

Bash shell使用let、(( ))和[]执行基本的算术操作。工具expr和bc可以用来执行高级操作。

  • let命令
    • let命令可以直接执行基本的算术操作。当使用let时,变量名之前不需要再添加$,例如:
      1
      2
      3
      4
      5
      $ no1=4; 
      $ no2=5;
      $ let result=no1
      $ echo $result
      > 9
    • let自操作
      1
      2
      3
      4
      $ let no1++
      $ let no1--
      $ let no+=6
      $ let no-=6
  • 操作符$[]和$(())
    • 操作符[]的使用方法和let命令一样:
      1
      2
      3
      4
      5
      $ result=$[ no1 + no2 ]
      //在[]中也可以使用$前缀,例如:
      $ result=$[ $no1 + 5 ]
      //也可以使用操作符(())
      $ result=$(( no1 + 50 ))
  • expr
    • expr同样可以用于基本算术操作
      1
      2
      $ result=`expr 3 + 4`
      $ result=$(expr $no1 + 5)
  • bc
    • 上述命令不支持浮点数计算,浮点数计算需要使用bc命令,bc是一个用于数学运算的高级实用工具,这个精密的计算器包含了大量的选项。我们可以借助它执行浮点数运算并使用一些高级函数:
      1
      2
      3
      4
      5
      6
      $ echo "4 * 0.56" | bc
      > 2.24
      $ no=54;
      $ result=`echo "$no * 1.5" | bc`
      $ echo $result
      > 81.0
    • 设定小数精度。
      1
      2
      3
      //在下面的例子中,参数scale=2将小数位个数设置为2。因此,bc将会输出包含两个小数位的数值:
      $ echo "scale=2;22/7" | bc
      > 3.14
    • 进制转换
      1
      2
      3
      4
      5
      6
      7
      //用bc可以将一种进制系统转换为另一种。来看看下面的代码是如何在十进制与二进制之间相互转换的:
      $ no=100
      $ echo "obase=2;$no" | bc
      > 1100100
      $ no=1100100
      $ echo "obase=10;ibase=2;$no" | bc
      > 100
    • 计算平方以及平方根。
      1
      2
      $ echo "sqrt(100)" | bc #Square root 
      $ echo "10^10" | bc #Square

文件描述符与重定向

文件描述符是与输入和输出流相关联的整数。最广为人知的文件描述符是stdin、stdout和stderr。文件描述符0、1以及2是系统预留的。

  • 文件描述符

    1
    2
    3
    0 —— stdin (标准输入)。
    1 —— stdout(标准输出)。
    2 —— stderr(标准错误)。
  • 重定向符号

    符号 说明
    < file 输入重定向,将<后的file文件内容作为command执行前的输入
    > file 或1>file 输出重定向,将标准正确输出覆盖到后面的file文件内
    >> file或1>>file 输出重定向,将标准正确输出追加到后面的file文件内
    2>file 输出重定向,将标准错误输出覆盖到后面的file文件内
    2>>file 输出重定向,将标准错误输出追加到后面的file文件内
    &>file 或 >file 2>&1 输出重定向,将标准正确输出和标准错误输出覆盖到后面的file文件内
    &>>file 或 >>file 2>&1 输出重定向,将标准正确输出和标准错误输出追加到后面的file文件内

如果你不想看到或保存错误信息,那么可以将stderr的输出重定向到/dev/null,保证一切都
会被清除得干干净净。

cat<log.txt,注意,<<EOF是固定用法,<<不是指输入重定向两次,命令<<EOF会将键入的、以EOF输入字符为标准输入结束的流内容作为输入。然后按照重定向执行顺序,第二步骤执行cat命令,输出<<EOF键入的内容,最后将其重定向进log.txt


数组与关联数组

数组允许脚本利用索引将数据集合保存为独立的条目。Bash支持普通数组和关联数组,前者使用整数作为数组索引,后者使用字符串作为数组索引。当数据以数字顺序组织的时候,应该使用普通数组,例如一组连续的迭代。当数据以字符串组织的时候,关联数组就派上用场了,例如主机名称。

值序列

值序列在循环中经常使用,我们可以使用{1..5}来得到1-5的数字序列,也可以使用{1,2,3,4,5}得到同样的序列,也可以用{a..z}得到a-z的集合。

1
2
3
4
5
6
7
8
$ echo {1..5}
> 1 2 3 4 5

$ for i in {1,2,3,4,5,6};
- do
- echo $i
- done
> 1 2 3 4 5 6

普通数组

  • 定义数组

    • 可以在单行中使用数值列表来定义一个数组
      1
      2
      //这些值将会存储在以0为起始索引的连续位置上
      $ array_var=(test1 test2 test3 test4)
    • 定义特定索引数组值
      1
      $ array_var[2]="test3"
    • 定义空数组并加值
      1
      2
      3
      $ array_var=();
      //加入someVar变量值
      $ array_var+=("$someVar");
  • 访问数组

    • 访问特定索引的数组元素内容
      1
      2
      3
      4
      5
      $ echo ${array_var[0]} 
      > test1
      $ index=5
      $ echo ${array_var[$index]}
      > test6
    • 以列表形式打印出数组中的所有值
      1
      2
      3
      4
      5
      $ echo ${array_var[*]} 
      > test1 test2 test3 test4 test5 test6
      //或
      $ echo ${array_var[@]}
      > test1 test2 test3 test4 test5 test6
    • 打印数组长度(即数组中元素的个数)
      1
      $ echo ${#array_var[*]}

      关联数组

      关联数组从Bash 4.0版本开始被引入。当使用字符串(站点名、用户名、非顺序数字等)作为索引时,关联数组要比数字索引数组更容易使用。
  • 定义关联数组

    在关联数组中,我们可以用任意的文本作为数组索引。首先,需要使用声明语句将一个变量定义为关联数组

    1
    2
    $ declare -A fruits_value
    $ fruits_value=([apple]='100 dollars' [orange]='150 dollars')
  • 访问数组

    • 用下面的方法显示数组内容
      1
      2
      $ echo "Apple costs ${fruits_value[apple]}"
      > Apple costs 100 dollars
    • 列出数组索引(对于普通数组,这个方法同样可行。)
      1
      2
      3
      4
      5
      $ echo ${!fruits_value[*]}
      > orange apple
      //或
      $ echo ${!fruits_value[@]}
      > orange apple

函数

函数和别名乍一看很相似,不过两者在行为上还是略有不同。alias是使用纯文本代替命令名,它在命令解析阶段就会把内容进行替换,由于替换过程完全是基于文本的,因而别名可以改变shell的语法;

函数的函数体是复合命令(bash),函数名在命令解析阶段并不会被替换,只是在命令执行阶段调用相应的函数处理对应的复合命令。

函数参数可以在函数体中任意位置上使用,而别名只能将参数放在命令尾部。

定义函数

函数的定义包括function命令、函数名、开/闭括号以及包含在一对花括号中的函数体。

  • function 关键字
    1
    2
    3
    4
    function fname()
    {
    statements;
    }
  • 无 function 关键字
    1
    2
    3
    4
    5
    6
    fname()
    {
    statements;
    }

    fname() { statement; }
  • 返回值
    在定义函数时,可以在函数体中使用return来定义返回值。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    fname() 
    {
    if [ $1 -eq 0 ];
    then
    return 0; #返回值
    else
    return 1; #返回值
    fi
    }

    我们知道,在使用&&和||连接符时,判断依据即为符号前后命令的成功与否,返回值等于0为成功,大于0为失败。

调用函数

1
2
3
$ fname ; //执行函数
或者
$ fname arg1 arg2 ; //函数参数可以按位置访问,$1是第一个参数,$2是第二个参数,

函数体中,要活用$1,$2,$*,$@等符号

  • 递归调用

    1
    2
    3
    4
    fname() {
    echo $1; fname hello;
    sleep 1;
    }

    自定义函数示例

    自定义函数需要定义在rc或者profile文件中,此文件的作用可详见理解 bashrc 和 profile

  • 对于export PATH=/opt/myapp/bin:$PATH,我们可以在.bashrc文件中定义一个新的函数,来简化这一追加路径的功能,使得export PATH=/opt/myapp/bin:$PATH等价于prepend PATH /opt/myapp/bin,其中$1=PATH,$2=/opt/myapp/bin:

    1
    2
    3
    4
    5
    6
    7
    8
    prepend() { [ -d "$2" ] && eval $1=\"$2\$\{$1:+':'\$$1\}\" && export $1 ; }
    // [ -d "$2" ]含义为先确认该函数第二个参数所指定的目录是否存在。
    //如果存在,eval表达式将第一个参数所指定的变量值设置成第二个参数的值加上$\{$1:+':'\$$1\}表达式的值
    // $1=\"$2\$\{$1:+':'\$$1\}\"
    // $1="$2${PATH:+':'$PATH}" //$1=PATH有值
    // $1="$2':'$PATH"
    // $1="/opt/myapp/bin:$PATH"
    //如果第二步执行成功,第三步,export,完成

逻辑关键字

循环-for、while、until

  • 面向列表的for循环

    list可以是一个字符串,也可以是一个值序列。
    1
    2
    3
    4
    for var in {1..50};
    do
    commands;#使用变量$var
    done
  • 迭代指定范围的数字

    1
    2
    3
    4
    for((i=0;i<10;i++))
    {
    commands; #使用变量$i
    }
  • 循环到条件满足为止

    1
    2
    3
    4
    5
    6
    //当条件为真时,while循环继续执行;当条件不为真时,until循环继续执行。
    //用true或者:作为循环条件能够产生无限循环。
    while condition
    do
    commands;
    done
  • until循环

    1
    2
    3
    4
    5
    6
    //在Bash中还可以使用一个特殊的循环until。它会一直循环,直到给定的条件为真。例如:
    x=0;
    until [ $x -eq 9 ]; #条件是[$x -eq 9 ]
    do
    let x++; echo $x;
    done

    判断-if else

  • if条件

    1
    2
    3
    4
    if condition; 
    then
    commands;
    fi
  • else if和else

    1
    2
    3
    4
    5
    6
    7
    8
    if condition; 
    then
    commands;
    else if condition; then
    commands;
    else
    commands;
    fi

    if和else语句能够嵌套使用。if的条件判断部分可能会变得很长,但可以用
    逻辑运算符将它变得简洁一些:
    [ condition ] && action; # 如果condition为真,则执行action
    [ condition ] || action; # 如果condition为假,则执行action

判断条件- [] 和 [[]]

判断条件通常被放置在封闭的中括号内。一定要注意在 [ 和 ] 与操作数之间有一个空格。如果忘记了这个空格,脚本就会报错。[$var -eq 0 ] or [ $var -eq 0]会报错

在[]和[[ ]]中,其实任何一个符号两边都要有空格,所以在判断的时候,不要吝啬空格。

  • 对数字变量或值进行算术条件比较

    1
    2
    [ $var -eq 0 ] #当$var等于0时,返回真
    [ $var -ne 0 ] #当$var不为0时,返回真
    • 其他重要的操作符如下

      • -gt:大于。

      • -lt:小于。

      • -ge:大于或等于。

      • -le:小于或等于。

        这些操作符只适用于数值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- 文件系统相关判断

我们可以使用不同的条件标志测试各种文件系统相关的属性。
- [ -f $file_var ]:如果给定的变量包含正常的文件路径或文件名,则返回真。
- [ -x $var ]:如果给定的变量包含的文件可执行,则返回真。
- [ -d $var ]:如果给定的变量包含的是目录,则返回真。
- [ -e $var ]:如果给定的变量包含的文件存在,则返回真。
- [ -c $var ]:如果给定的变量包含的是一个字符设备文件的路径,则返回真。
- [ -b $var ]:如果给定的变量包含的是一个块设备文件的路径,则返回真。
- [ -w $var ]:如果给定的变量包含的文件可写,则返回真。
- [ -r $var ]:如果给定的变量包含的文件可读,则返回真。
- [ -L $var ]:如果给定的变量包含的是一个符号链接,则返回真
- [ -s $var ]:如果给定的变量包含的文件大小大于0字节,则返回真
- [ $var1 -nt $var2 ]:new than操作,如果给定的变量包含的文件1比文件2新,则返回真
- [ $var1 -ot $var2 ]:old than操作,如果给定的变量包含的文件1比文件2旧,则返回真
- [ $var1 -ef $var2 ]:equal file操作,如果给定的变量包含的文件1和文件2为同一文件,则返回真
- 字符串比较

**进行字符串比较时,最好用双中括号**,因为有时候采用单个中括号会产生错误。
- 测试两个字符串是否相同
//当str1等于str2时,返回真。也就是说,str1和str2包含的文本是一模一样的。 [[ $str1 = $str2 ]] //这是检查字符串是否相同的另一种写法。 [[ $str1 == $str2 ]] //如果str1和str2不相同,则返回真。 [[ $str1 != $str2 ]]
1
2
3
4
5
		> 注意在=前后各有一个空格。如果忘记加空格,那就不是比较关系了,而是变成了赋值语句。
- 字符串比较

字符串是依据字符的ASCII值进行比较的。例如,A的值是0x41,a的值是0x61。因此,A
小于a,AAa小于Aaa。
//如果str1的字母序比str2大,则返回真。 [[ $str1 > $str2 ]] //如果str1的字母序比str2小,则返回真。 [[ $str1 < $str2 ]]
1
- 判断空串
[[ -z $str1 ]] //如果str1为空串,则返回真。 [[ ! -z $str1 ]] //如果str1为空串,则返回假。与-n 等价 [[ -n $str1 ] //如果str1不为空串,则返回真。
1
2
3
4
5


- 逻辑与和逻辑或

- -a是逻辑与操作符,-o是逻辑或操作符。可以按照下面的方法结合多个条件进行
[ $var1 -ne 0 -a $var2 -gt 2 ] #使用逻辑与-a [ $var1 -ne 0 -o $var2 -gt 2 ] #逻辑或-o
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- [] 和 [[ ]]的区别

二者基本相等,除了下述几点

- 逻辑运算符不同
- []使用 -a 和 -o 来表达 与 和 或,不识别&& 和 ||
- [[ ]]使用 && 和 || 来表达 与 和 或,不识别-a 和 -o

- ==含义不同

- 在[[ ]]中,表达式"=="和"!="的右边其实会被当做pattern匹配,只不过不是正则匹配,是通配符匹配(即?表示匹配单个字符,*表示匹配零个一个或多个字符)。

- [[ ]]支持正则匹配

- 在[[ ]]中,表达式"=~"的右边会被当做正则匹配
- 不过要注意,使用"=~"时,右边表达式不需要引号,如:[[ '$var' =~ a* ]]

> 使用建议,无论是[]还是[[ ]],都建议对其变量使用双引号包围,换句话说,能做字符比较的时候,不要做数值比较。例如`var='shell script' [ $var = "shell script" ]` 会报错,因为变量不加双引号,相当于[ shell script = "shell script" ],这显然是错误的,所以应该加上引号`[ "$var" = "shell script" ]`

> 使用建议,使用-eq数值比较的时候,可以在操作符两边同时+0,避免变量为空报错,当日,一边为常数的话可以不用+0:` [ $((a+0)) -le 1]`

> test命令可以用来测试条件。用test可以避免使用过多的括号,增强代码的可读性。[]中的测试条件同样可以用于test命令。`if [ $var -eq 0 ]; then echo "True"; fi` 等价于 `if test $var -eq 0 ; then echo "True"; fi`
---
## Linux/unix文件系统
### 文件权限

文件权限和所有权是Unix/Linux文件系统的显著特性之一。这些特性能够在多用户环境中保护你的个人信息。每一个文件都拥有多种类型的权限。在这些权限中,我们通常要和三组权限打交道:用户、用户组以及其他用户。

用户(user)是文件的所有者,通常拥有所有的访问权。用户组(group)是多个用户的集合(由系统管理员指定),可能拥有文件的部分访问权。其他用户(others)是除文件所有者或用户组成员之外的任何人。

ls命令的-l选项可以显示出包括文件类型、权限、所有者以及组在内的多方面信息:

$ ls -l

-rw-r–r– 1 slynux users 2497 2010-02-28 11:22 bot.py
drwxr-xr-x 2 slynux users 4096 2010-05-27 14:31 a.py
-rw-r–r– 1 slynux users 539 2010-02-10 09:11 cl.pl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
上述代码中,第1列表明了文件类型。字符串slynux users分别对应用户和用户组。在这里,slynux是文件所有者,也是组成员之一。

其中文件类型有如下几种:
- -:普通文件。
- d:目录。
- c:字符设备。
- b:块设备。
- l:符号链接。
- s:套接字。
- p:管道

接下来的9个字符可以划分成三组,每组3个字符(--- --- ---)第一组的3个字符对应用户权限(所有者),第二组对应用户组权限,第三组对应其他用户权限。这9个字符(即9个权限)中的每一个字符指明是否其设置了某种权限。如果已设置,对应位置上会出现一个字符,否则出现一个-,表明没有设置对应的权限。

有3种常见的字符。

- r(read):如果设置,表明该文件、设备或目录可读。
- w(write):如果设置,表明该文件、设备或目录可以被修改。对于目录而言,此权限指定了是否可以在目录下创建或删除文件。
- x(execute):如果设置,表明该文件可执行。对于目录而言,此权限指定了能否访问目录下的文件。

同时这三组权限含义如下:

- 用户(权限序列:rwx------):定义了用户权限。通常来说,对于数据文件,用户权限是rw-;对于脚本或可执行文件,用户权限是rwx。用户还有一个称为setuid(S)的特殊权限,它出现在执行权限(x)的位置。setuid权限允许可执行文件以其拥有者的权限来执行,即使这个可执行文件是由其他用户运行的。具有setuid权限文件的权限序列可以是这样的:-rwS------。
- 用户组(权限序列:---rwx---):第二组字符指定了组权限。组权限中并没有setuid,但是有一个setgid(S)位。它允许使用与可执行文件所属组权限相同的有效组来运行该文件。但是这个组和实际发起命令的用户组未必相同。例如,组权限的权限序列可以是这样的:----rwS---。
- 其他用户(权限序列:------rwx):最后3个字符是其他用户权限。如果设置了相应的权限,其他用户也可以访问特定的文件或目录。作为一种规则,通常将这组权限设置为---。

> 目录有一个叫作粘滞位(sticky bit)的特殊权限。如果目录设置了粘滞位,只有创建该目录的用户才能删除目录中的文件,就算用户组和其他用户也有写权限,仍无能无力。粘滞位出现在其他用户权限组中的执行权限(x)位置。它使用t或T来表示。如果没有设置执行权限,但设置了粘滞位,就使用T;如果同时设置了执行权限和粘滞位,就使用t。例如:
> `------rwt , ------rwT`
> 设置目录粘滞位的一个典型例子就是/tmp,也就是说任何人都可以在该目录中创建文件,
> 但只有文件的所有者才能删除其所创建的文件。

> 可使用chmod命令设置文件权限。具体参见博文 [常用shell命令导航(Linux shell脚本攻略笔记)](https://my.oschina.net/u/4133922/blog/3077074 "常用shell命令导航(Linux shell脚本攻略笔记)")

## 有用的函数或者脚本

### 持续运行命令直至执行成功
有时候命令只有在满足某些条件时才能够成功执行。例如,在下载文件之前必须先创建该文件。这种情况下,你可能希望重复执行命令,直到成功为止。

//定义如下函数:
repeat()
{
while true
do
$@ && return
done
}
//函数repeat()中包含了一个无限while循环,该循环执行以函数参数形式(通过$@访问)传入的命令。如果命令执行成功,则返回,进而退出循环。

1
2
在大多数现代系统中,true是作为/bin中的一个二进制文件来实现的。
这就意味着每执行一次之前提到的while循环,shell就不得不生成一个进程。为了避免这种情况,可以使用shell的内建命令:,该命令的退出状态总是为0:

repeat() { while :; do $@ && return; done }

1
加入延时

//每30秒才会运行一次
repeat() { while :; do $@ && return; sleep 30; done }

---
0%