0%

Shell 其他知识点

eval

eval command-line,shell 在执行命令时会扫描两次。

1
2
3
4
5
6
7
8
$ eval echo hello  # 看上去没什么区别
hello
$ pipe="|"
$ ls $pipe wc -l
ls: -l: No such file or directory
ls: wc: No such file or directory
ls: |: No such file or directory
$ eval ls $pipe wc -l

shell 先进行管道操作和重定向再做变量替换,所以这里会报错。

eval 命令经常用于在一个或多个变量内构建命令行的 shell 程序中。如果变量包含必须由 shell 解释的任何字符,则 eval 是必不可少的。命令终止符(;|&)、I/O 重定向(<>)和引号字符是必须直接出现在命令行上才能对 shell 具有特殊含义的字符。

1
2
3
# 获取命令行最后一个参数,之前是用 `shift` 实现的
$ eval echo \$$# # 第二次扫描时反斜线被移除了,因为第一次的时候已经处理了
$ eval echo \${$(($#-1))} # 倒数第二个
1
2
3
# 假设 arg 是个数字,想打印数字对应的位置参数
$ eval echo \$$arg # 如果数字大于 9 呢?
$ eval echo \${$arg}

可以使用 eval 达到指针的效果

1
2
3
4
5
$ x=100
$ ptrx=x
$ eval echo \$$ptrx # Dereference ptrx
$ eval $ptrx=50
$ echo $x

wait

如果将命令移到后台执行,则该命令行将在独立于当前 shell 的子 shell 中运行(该作业称为异步运行)。然而,在某些情况下,可能需要等待后台进程(也称为子进程,因为它是从当前 shell(父进程)生成的)完成再继续执行。

process-id```,如果没有进程 ID,shell 会等待所有子进程完成再继续执行。当前 shell 的执行将暂停,直到一个或多个进程完成执行。
1
2
3
4
5

```shell
$ sleep 10 & # send to background
[1] 2133 # job number and process id from shell
$ wait 2133 # wait for sleep to finish,when sleep is done, prompt is returned

$!

如果只有一个进程在后台运行,那么不带任何参数等待就足够了。但是,如果正在运行多个后台命令,并且想要等待最近启动的命令,则可以将最新后台命令的进程 ID 作为特殊变量 $! 进行访问。

wait $! 等待最后一个放到后台执行的程序完成。可以使用中间变量保存多个进程 id。

trap

在 Shell 编程中,trap 是一个用于捕捉和处理信号的命令。信号是操作系统与进程之间的一种通信方式,当某些事件(如用户中断、脚本退出、或特定的系统事件)发生时,操作系统会向正在运行的进程发送信号。通过 trap 命令,可以在特定信号到来时,执行指定的命令或脚本,而不是让默认行为发生。这样可以用来做清理工作、保存数据、或者执行其他必要的操作。

trap commands signals 其中 commands 是一个或多个命令,每当接收到 signals 指定的任何信号时将执行这些命令。

trap "rm $WORKDIR/work1$$ $WORKDIR/dataout$$; exit" INT,收到 INT 也就是 Ctrl+c 时的行为,如果这里没有 exit,那么程序会继续执行。

如果指定的 trap sequence 也称为陷阱处理程序 trap handler)包含多个命令,则必须将其括在引号中。另外,shell 在执行 trap 命令时扫描命令行,并在收到列出的信号之一时再次扫描命令行。

在前面的示例中,WORKDIR$$ 的值将在执行 trap 命令时被替换。如果希望在收到信号时进行此替换,则可以将命令放在单引号内。

没有参数的 trap

直接调用 trap 会列出用户定义的或者修改的 trap handler

1
2
3
4
$ trap 'echo logged off at $(date) >>$HOME/logoffs' EXIT  # 这里使用单引号就是防止在定义 trap 时 shell 执行 date
$ trap
$ Ctrl + d
$ cat $HOME/logoffs

忽略信号

1
2
$ trap "" SIGINT
$ trap : SIGINT

如果 trap handler 为空,就相当于忽略了对于的信号,trap "" SIGINTtrap 可以使用信号编号、缩略形式(INT)、完整名称(SIGINT)来注册信号。建议使用完整形式方便阅读。

如果忽略某个信号,所有子 shell 也会忽略该信号。但是,如果指定信号处理程序操作,则所有子 shell 在收到该信号时都会自动采取默认操作,而不是新的代码序列(trap handlers)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash

# 在父 Shell 中设置一个 trap,捕获 SIGINT(Ctrl+C)
trap "echo '父 Shell 捕获到 SIGINT'" SIGINT

# 启动子 Shell
(
# 子 Shell 中没有设置 trap
echo "子 Shell 中,按 Ctrl+C"
sleep 10
)

echo "子 Shell 结束,回到父 Shell,按 Ctrl+C"
sleep 10

恢复 traps

trap HUP INT 如果忽略 handlers,就意味着使用系统默认的处理程序。

I/O

在 Shell 编程中,>& 是一种用于重定向输出和错误的语法,通常与文件描述符(file descriptor)结合使用。符号 >& 指定输出重定向到与后面的文件描述符关联的文件。

>&2 把标准输出重定向到标准错误输出,如果想把标准输出和标准错误都重定向到文件可以使用 command > foo 2>> foo 或者 command > foo 2>&1,第二个是说把标准输出定向到文件,再把标准错误输出重定向到标准输出也就是标准输出。
shell 从左到右进行解析,所以不能写成 command 2>&1 > foo

如果想重定向当前 shell 的 I/O 怎么办?使用 execexec < datafileexec > /tmp/outputexec 2> /tmp/errors,会影响后续所有命令的 I/O 行为。

<&- and >&-

用来关闭标准输入和输出,如果前面跟一个文件描述符相关的文件就会被关闭。

ls >&-,没有任何输出,不是很常用。

In-line input Redirection

>><< 是不同的,前者是追加重定向;后者是 here document,将多行文本作为输入传递给命令。

1
2
3
4
5
# EOF 可以换成其他东西,首尾要一样
$ wc -l <<EOF
heredoc> line 1
heredoc> line 2
heredoc> EOF

In-line input redirection—also referred to as here documents by some programmers—is a powerful feature when used inside shell programs。

here doc 中的特殊字符 $\、反引号会被 shell 处理,如果不想让 shell 处理这些文件,可以在 end-of-document word前加一个反斜线。

1
2
3
$ cat <<\FOOBAR
heredoc> `date`
heredoc> FOOBAR

选择 << 后面的单词(end-of-document word)时要小心。一般来说,只要确保它足够奇怪,这样它意外出现在后续数据行中的可能性就很小了。

<<- 会移除 here doc 中的所有的 tab,这样的好处是可以在 shell 中很好的格式化数据,但是输出时却不包含这些tab

1
2
3
4
$ cat <<-END
heredocd> Indented lines
heredocd> because tabs are cool
heredocd> END

Shell Archives

使用 in-line input redirection,可以创建 shell archive 文件。

通过这种技术,可以将一个或多个相关的 shell 程序放入一个文件中,然后使用标准 Unix 邮件命令发送给其他人。收到存档后,可以通过将其作为 shell 程序调用来”解包”。

1
2
3
4
5
6
7
8
9
10
11
12
13
# shell archives

# scripts 1
echo Extracting script1
cat >script <<\EOF
ls -la
EOF

# scripts 2
echo Extracting script2
cat >script <<\EOF
ls -la
EOF

可以把这个文件发送给别人,相当于程序分发了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 
# Program to create a shell archive
# from a set of files
#

echo "#"
echo "# To restore, type sh archive"
echo "#"

for file
do
echo
echo "echo Extracting $file"
echo "cat >$file <<\THE-END-OF-DATA"
cat $file
echo "THE-END-OF-DATA"
done

Functions

函数是在当前 shell 执行的。

所有现代 shell 都支持定义函数,name () { command; command; },如果是单行定义,则第一个花括号有要有空格,最后一个命令要有分号。

函数只能在当前 shell 中使用,不能传递给 subshell。函数仅存在于定义它们的 shell 中,因此不能将它们传递给子 shell。由于该函数是在当前 shell 中执行的,因此对当前目录或变量所做的更改在该函数完成执行后仍然保留,就像使用 . 一样。

函数可以多行定义。

Removing a function definition

unset -f add

return

在函数中使用 exit,不仅会退出函数还会退出当前 shell,使用 return n 退出函数,n 表示函数退出状态,如果没有写,那么使用最后一个命令的返回值。

type

执行的是 functin、shell built-in function、a standard Unix Command or alias 有时非常重要,可以使用 type 查看。