Bash

内置命令

@2019-02-20 新版功能: 创建,set

在 @2019-02-24 版更改: 增加 set -eox

set

  • -e

    set -o errexit。一旦命令退出值非 0,则立刻中断脚本执行。 对管道无效,参考 **`set -o pipefail`。**

  • -o

    • pipefail 如果未设置(缺省情况),则管道组合命令的最后一个命令为返回值。 如果设置了,则只要管道中的某个子命令失败,则整个组合命令也失败。例如:

      $ bash -c 'set -e; foo | echo "a"; echo $?; echo ">> done"'
      a
      bash: foo: 未找到命令
      0
      >> done
      
      $ bash -c 'set -e; set -o pipefail; foo | echo "a"; echo $?; echo ">> done"'
      a
      bash: foo: 未找到命令
      
  • -u

    如果变量未定义,则尝试显示该变量时会报错(非交互式 shell 会退出)。

    $ bash -c 'set -u; echo $u; echo "done"'
    bash: u: unbound variable
    
  • -x

    set -o xtrace。先输出 $PS4(+)以及要执行的命令和参数,然后执行命令。 对调试很有帮助。

tips w/ examples

@2019-02-22 新版功能: 创建

cd

$ pwd
/
$ cd /tmp
$ cd -
/
$ cd -
/tmp

Loop

$ for i in {1..2}; do echo $i; done
1
2

扩展 bash 内置命令

@2014-01-01 新版功能: 创建

一些常用的操作如 sleep,cat 等一般是通过调用单独的外部命令来执行。 在对一些 shell 脚本做优化时,或者让系统进程树更清晰,或者尽可能的减少进程 fork 的次数时总是想尽可能的通过调用 bash 的内置命令来实现类似的功能,在使用内置 命令实现过于复杂时通过编写扩展编译出动态库来扩展 bash 内置命令也是一个办法。

对于 sleep,可以通过 bash 内置的 read 命令的超时功能来实现,如下的例子通过创建 命名管道后读写该管道来实现 sleep 的功能:

waitfifo=/tmp/fifo-demo-only
[ -p ${waitfifo} ] || mkfifo ${waitfifo}
while true ; do
    read -t 1 <>${waitfifo}
done

其他的功能则需要尽可能的利用 bash 已有的内置命令结合一些技巧来实现。这里我们 介绍另一种方法,通过 bash 内置的 enable 命令的 “-f” 参数能动态加载代码来实现 任意内置命令。还是以 sleep 为例,假设其源代码(附在本节最后)为 sleep.c,参考如下过程演示:

$ enable -n sleep # 禁止 sleep 内置命令,如果有
$ type sleep
sleep 是 /usr/bin/sleep
$ gcc -DHAVE_CONFIG_H -fPIC -shared -Wl,-soname,sleep sleep.c -o sleep.so
$ enable -f ./sleep.so  sleep
$ type sleep
sleep 是 shell 内嵌
$ sleep -h
sleep: 用法:sleep seconds[.fraction]

事实上,对于 Gentoo 系统,只需要对 bash 的 USE flags 加上 plugins 即可拥有 bash 源代码里已经实现了的动态加载内置命令,查看目录 /usr/lib/bash/

$ equery uses bash
...
 * Found these USE flags for app-shells/bash-4.2_p45:
...
 + + plugins      : Add support for loading builtins at runtime via 'enable'
...

对 Ubuntu 则需要先通过 apt-get install bash-builtins 安装相应的头文件后再来 编译相关的代码。

sleep.c 的源代码如下:

/**
 * @file
 * @brief sleep -- sleep for bash builtin
 */
#include <unistd.h>

// for Gentoo, replace bash-plugins w/ bash if Ubuntu
#include <bash-plugins/shell.h>
#include <bash-plugins/builtins.h>

int sleep_builtin (WORD_LIST *list)
{
    long    sec, usec;
    struct timespec tv;

    if (list == 0) {
        builtin_usage();
        return(EX_USAGE);
    }

    if (*list->word->word == '-' || list->next) {
        builtin_usage ();
        return (EX_USAGE);
    }

    if (uconvert(list->word->word, &sec, &usec)) {
        tv.tv_sec = sec;
        tv.tv_nsec = usec * 1000;

        nanosleep(&tv, NULL);
        return(EXECUTION_SUCCESS);
    }

    builtin_error("%s: bad sleep interval", list->word->word);
    return (EXECUTION_FAILURE);
}

static char *sleep_doc[] = {
    "Suspend execution for specified period.",
    "sleep suspends execution for a minimum of SECONDS[.FRACTION] seconds.",
    (char *)NULL
};

struct builtin sleep_struct = {
    "sleep",
    sleep_builtin,
    BUILTIN_ENABLED,
    sleep_doc,
    "sleep seconds[.fraction]",
    0
};

受限的 BASH (rbash)

@2013-09-05 新版功能: 创建

如果 bash 以 rbash 为程序名启动或者命令行参数有 -r 选项,则启动的这个 shell 会在某些功能上受限制.具体表现为如下操作都不能做:

  • 通过 cd 来改变工作目录
  • 设置或取消环境变量: SHELL, PATH, ENV, BASH_ENV
  • 命令名中不能包含目录分隔符 ‘/’
  • 包含有 ‘/’ 的文件名作为内置命令 ‘.’ 的参数
  • hash 内置命令有 -p 选项时的文件名参数包含 '/'
  • 在启动时通过 shell 环境导入函数定义
  • 在启动时通过 shell 环境解析 SHELLOPTS 的值
  • 使用 <,<|, <>, >&, &>, >>; 等重定向操作符
  • 使用 exec 内置命令
  • 通过 enable 内置命令的 -f 和 -d 选项增加或删除内置命令
  • 使用 enable 内置命令来禁用或启用 shell 内置命令
  • 执行 command 内置命令时加上 -p 选项
  • 通过 set +rset +o restricted 关闭受限模式

rbash 一般是指向 bash 的符号链接,如下:

$ ls -l /bin/rbash
lrwxrwxrwx 1 root root 4 May  3 13:04 /bin/rbash -> bash

通过设置 PATH 环境变量为指定的目录,可以控制用户只能执行这些目录下的命令。

需要注意的是, rbash 提供的受限环境的安全程度取决于用户能执行的命令,很多 命令都能调用外部命令,从而导致逃逸出受限环境。例如:

  • more -1 /etc/passwd,然后 !bash。 对 less, man 等命令同样有效。
  • vi 等编辑器能通过 : 执行外部命令
  • awk 'BEGIN {system("/bin/sh")}'
  • find /etc/ -name passwd -exec /usr/bin/awk 'BEGIN {system("/bin/sh")}' ;
  • echo "evil script code" | tee script.sh

如果能执行脚本,如 python, perl 等则有很多种方式启动一个 shell。

因此,通过 rbash 构建一个安全的受限环境需要花不少功夫,需要严格控制用户能 使用的命令。更安全的做法还是通过 chroot jail 等方式。

cd 的前向不兼容行为

@2018-04-18 新版功能: 创建

$ ls -F/
a/  ab
$ cd a*
-bash: cd: too many arguments

然而在之前的版本这个 cd 操作是可以正常切换到 a/ 目录的。原因在

+/* Define CD_COMPLAINS if you want the non-standard, but sometimes-desired
+   error messages about multiple directory arguments to `cd'. */
+#define CD_COMPLAINS
+

新增的宏 CD_COMPLAINS 如果定义了,那么在 cd 后接多个参数时就会直接报错。