Bash

Quoting

IFS=$'\n'

文档中这么说的:

Bash introduces two additional forms of quoting. The first is $'...' which acts like single quotes except that backslash-escaped combinations are expanded as specified by the ANSI C standard. This allows a convenient way to embed nonprintable characters into strings, or to pass them as arguments.

Aliases

\rm file

跳过rm的任何alias。在Bash手册的alias节:

The first word of each simple command, if unquoted, is checked to see if it has an alias.

Aliases不被子进程继承,这一点在Bash的官方文档里面似乎没有指出。

Grouping Commands

fordo...done可以用{}来代替(Grouping Commands):

for file in /usr/sbin/sendmail /usr/bin/mailq /usr/bin/newaliases; { echo -n "$file: " && rpm -q --whatprovides ${file}; }

在Bashref和ABS里面尚未看到有相关的说明。

read

$ read -p input:

input:abc

$ echo $REPLY

abc

$ read -p input: 2>/dev/null

abc

$ echo $REPLY

abc

可以看到如果把stderr重定向到null的话,read的提示符就出不来了。没有看源代码,我猜测-p参数后的提示符是写到stderr的。如果在运行脚本的时候加上2>/dev/null,而正好脚本里又有read -p <prompt>命令,那么这个提示符是看不到的。可用echo <prompt>代替-p prompt。这个应该是有意这么设计的,不要让提示符影响stdout的内容。

Command Substitution

我用

<<< $(tail -n +3 /proc/net/dev)

来做一个输入定向,本来是多行内容,这样之后合并成了一行。经过研究,发现是$()的问题。Bash的文档中说:

Embedded newlines are not deleted, but they may be removed during word splitting.

以及:

If the substitution appears within double quotes, word splitting and filename expansion are not performed on the results.

就是说如果没有用双引号把Command Substitution的内容保护起来,会做word splitting,然后中间的换行符会删除掉,所以就连成一行了。改成

<<< "$(tail -n +3 /proc/net/dev)"

就好了,输出还是多行。

匹配所有.开头的文件

ls -A | grep '^\.'

或者

shopt -s extglob

shopt -s dotglob

ls -ad *(.*)

ls -ad .!(.|)

但是在打开dotglob的情况下,*(.*)不匹配...,而.*是匹配的:

$ diff <(ls -d *(.*)) <(ls -d .*)

0a1,2

> .

> ..

这个需要进行澄清,应该也是bug。还有,在不打开dotglob的情况下,*(.*)什么都不匹配:

$ shopt -u dotglob

$ ls -d *(.*)

ls: cannot access *(.*): No such file or directory

已汇报bug

文本部分输出

输出前20个字符:

$ cut -b -20 pi

3.141592653589793238

或者:

$ grep -oE '^.{20}' pi

3.141592653589793238

或者:

$ read -n 20 <pi && echo $REPLY

3.141592653589793238

输出第30~40个字符呢:

$ cut -b 30-40 pi

27950288419

复制文件并覆盖链接

如果当前目录下有一个符号链接,想把这个链接改成被链文件的副本,一般是删除链接再把原文件复制过来。其实可以用cp--remove-destination选项一次复制的:

$ ls -l foo

lrwxrwxrwx 1 tux tux 6 2011-12-31 20:02 foo -> ../foo

$ cp ../foo .

cp: `../foo' and `./foo' are the same file

$ ls -l foo

lrwxrwxrwx 1 tux tux 6 2011-12-31 20:02 foo -> ../foo

$ cp --remove-destination ../foo .

$ ls -l foo

-rwxr-x--x 1 tux tux 0 2011-12-31 20:03 foo

时间

手动设置系统时间(来自info date):

date -d "$(LC_TIME=C date)"

后面的date运行结果从一个正确的机器的来,例如:

Sun Apr 1 22:40:43 CST 2012

>

重定向的时候,如果内容是整数,那么整数和>之间要有空格,否则整数会被Bash理解为文件描述符。

$ echo 2>a

$ cat a

$ echo 2 >a

$ cat a

2

用find .是不行的,要如下:

find */* -type l

!

echo "haha!"这个语句在交互方式下执行是不行的,因为!是History expansion character,只能用\或者单引号来保护。但是这个语句在脚本里面是正确的,因为和历史相关的东西在脚本下都失效的。

Here Document

最后的delimiter必须前后没有空白和其它字符,并且独占一行。

并行执行命令

例如,把当前目录下的图片都旋转90度:

while read -r img; do (mogrify -rotate 180 "$img" &) ; done < <(ls)

do后面的语句要加括后才行,否则语法错误。

还可以用xargsparallel等工具。