关于shell:Bash的隐藏功能

Hidden features of Bash

shell脚本通常用作胶水,用于自动化和简单的一次性任务。您最喜欢的bash shell/脚本语言的"隐藏"特性是什么?

  • 每个答案一个功能
  • 给出功能的示例和简短描述,而不仅仅是指向文档的链接
  • 用粗体标题作为第一行标记功能

参见:

  • C的隐藏特征
  • C的隐藏特征#
  • C++的隐藏特性
  • 德尔菲的隐藏特征
  • Python的隐藏特征
  • Java的隐藏特性
  • javascript的隐藏特性
  • 红宝石的隐藏特征
  • PHP的隐藏功能
  • Perl的隐藏特性
  • VB.NET的隐藏功能

插入前一行的最终参数

alt-.是有史以来最有用的组合键,试试看,不知为什么没有人知道这个组合键。

一次又一次地按它以选择旧的最后一个参数。

很好,当你想对刚才用过的东西做点别的事情的时候。


如果要在注销后保持进程运行:

disown -h

是一个内置的有用的bash。与nohup不同,您可以在已经运行的进程上运行disown

首先,用control-z停止工作,从ps获取pid(或使用echo $!,使用bg将其发送到后台,然后使用带-h标志的disown

别忘了做你工作的背景,否则你注销后会被杀的。


手册扩展部分列出的几乎所有内容

尤其是参数展开:

1
2
3
4
5
6
7
8
9
$ I=foobar
$ echo ${I/oo/aa} #replacement
faabar
$ echo ${I:1:2}   #substring
oo
$ echo ${I%bar}   #trailing substitution
foo
$ echo ${I#foo}   #leading substitution
bar


我最喜欢的是:

1
sudo !!

用sudo重新运行上一个命令。


更多魔术组合:

  • ctrl+r通过命令历史记录开始"反向增量搜索"。当您继续键入时,它将检索包含您输入的所有文本的最新命令。

  • t完成了迄今为止输入的单词,如果它不含糊的话。

  • TAB TAB列出了迄今为止键入的单词的所有补全。

  • alt+*插入所有可能的完成操作,这特别有用,例如,如果您刚刚输入了一个具有潜在破坏性的命令,使用通配符:

    rm -r source/d*.calt+*rm -r source/delete_me.c source/do_not_delete_me.c

  • ctrl+alt+e在当前行上执行别名、历史记录和外壳扩展。换句话说,当前行将重新显示,因为它将由shell处理:

    ls $HOME/tmpctrl alt+els -N --color=tty -T 0 /home/cramey


返回历史记录命令和参数

可以使用!运算符有选择地访问以前的命令和参数。当你在长途跋涉时,它是非常有用的。

您可以使用history检查最后的命令。

您可以使用以前的命令,其中!nhistory中命令的索引,负数从历史上最后一个命令倒数。

1
2
3
ls -l foo bar
touch foo bar
!-2

您可以将前面的参数与!:一起使用,零是命令,>=1是参数。

1
2
3
ls -l foo
touch !:2
cp !:1 bar

你可以和!:结合

1
2
3
4
touch foo bar
ls -l !:1 !:2
rm !-2:1 !-2:2
!-2

也可以使用参数范围!:-

1
2
touch boo far
ls -l !:1-2

其他!特殊修饰语为:

  • *表示所有的论点

    1
    2
    ls -l foo bar
    ls !*
  • 第一个论点的^(!:1==!^)

  • 最后一个论点

    1
    2
    ls -l foo bar
    cat !$ > /dev/null


我喜欢-x特性,允许查看脚本中的内容。

1
bash -x script.sh

SECONDS=0; sleep 5 ; echo"that took approximately $SECONDS seconds"

SECONDS

Each time this parameter is
referenced, the number of seconds
since shell invocation is returned.
If a value is assigned to SECONDS,
the value returned upon subsequent
references is the number of seconds
since the assignment plus the value
assigned. If SECONDS is unset, it
loses its special properties, even if
it is subsequently reset.


这是我的最爱之一。这将选项卡完成设置为不区分大小写。它非常适合快速输入目录路径,特别是在默认情况下文件系统不区分大小写的Mac上。我把这个放在我的文件夹里。

1
set completion-ignore-case on


特殊变量随机:

1
2
3
4
5
if [[ $(($RANDOM % 6)) = 0 ]]
    then echo"BANG"
else
    echo"Try again"
fi


快速修正输入错误(特别适用于使用命令历史记录和滚动命令会很糟糕的慢速连接上的长命令):

1
2
3
$ cat /proc/cupinfo
cat: /proc/cupinfo: No such file or directory
$ ^cup^cpu

也可以尝试一下!:s/old/new,它在以前的命令中用新的替换旧的。

如果要替换多个事件,可以使用!:gs/old/new进行全局替换。

您可以在任何历史事件中使用gss命令,例如

1
!-2:s/old/new

在第二个命令到最后一个命令中用new代替old


正则表达式处理

最近发布的bash具有正则表达式匹配功能,因此可以执行以下操作:

1
2
3
if [["mystring" =~ REGEX ]] ; then  
    echo match
fi

其中regex是一个原始的正则表达式,格式由man re-u格式描述。

任何带括号部分的匹配项都存储在bash-rematch数组中,从元素1开始(元素0是整个匹配字符串),因此您也可以使用它进行regex支持的解析。


CtrLCtrLBakBd

这将把当前命令加载到变量visual中定义的编辑器中。这对于长命令非常有用,比如这里列出的一些命令。

要使用vi作为编辑器:

1
export VISUAL=vi


以下是我的两个最爱:

要在不真正执行脚本的情况下检查语法,请使用:

1
bash -n script.sh

回到最后一个目录(是的,我知道pushd和popd,但这更快)

1
cd -


数组:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash

array[0]="a string"
array[1]="a string with spaces and "quotation" marks in it"
array[2]="a string with spaces, "quotation marks" and (parenthesis) in it"

echo"There are ${#array[*]} elements in the array."
for n in"${array[@]}"; do
    echo"element = >>${n}<<"
done

有关阵列(以及其他高级bash脚本编写工具)的更多详细信息,请参阅《高级bash脚本编写指南》。


bash man页中的魔术键组合:

  • ctrl+actrl+e分别将光标移动到当前行的开头和结尾。

  • ctrl+talt+t将光标前面的字符和单词与当前字符和单词进行换位,然后向前移动光标。

  • alt+ualt+l将当前单词(从光标到结尾)转换为大写和小写。

    提示:按alt+&ndash;,然后按以下任一命令转换当前单词的开头。

奖金man小费:

  • 在查看man页时,使用/搜索页面内的文本。使用n向前跳到下一个匹配,或使用n向前跳到上一个匹配。

  • 利用man页中的特定命令或子节的格式,加快搜索速度:

    o不要键入/history expansion来查找该节,而是尝试/^ history,使用插入符号(^来查找仅以"history"开头的行。

    o尝试使用/ ;read(带有一些前导空格)来搜索该内置命令。内置内容总是缩进到man页中。


使用中缀布尔运算符

如果:

1
2
3
if [ 2 -lt 3 ]
    then echo"Numbers are still good!"
fi

那-看起来有点难看。不是很现代。如果在布尔表达式周围使用双括号,则可以使用普通布尔运算符!

1
2
3
if [[ 2 < 3 ]]
    then echo"Numbers are still good!"
fi


在显示bash提示之前运行命令

在"prompt_command"env变量中设置一个命令,它将在每次提示之前自动运行。例子:

1
2
3
4
5
6
[lsc@home]$ export PROMPT_COMMAND="date"
Fri Jun  5 15:19:18 BST 2009
[lsc@home]$ ls
file_a  file_b  file_c
Fri Jun  5 15:19:19 BST 2009
[lsc@home]$ ls

对于下一个愚人节,将"export prompt_command=cd"添加到某人的.bashrc然后坐下来观察混乱的展开。


export TMOUT=$((15*60))

空闲15分钟后终止bash,设置为0以禁用。我通常把这个放在根帐户的~/.bashrc上。它在管理您的箱子时很方便,您可能会忘记在离开终端前注销。


撤消

c-s--control shift minusundo es typing actions.

杀戮/要害

任何删除操作c w(删除前一个字)、c k(删除到行尾)、c u(删除到行首)等…将删除的文本复制到kill环,您可以使用:c y粘贴最后一个kill,并使用alt y循环(并从中粘贴)已删除项目的环。


通过设置FIGNORE变量,可以在制表完成时忽略某些文件。

例如,如果您有一个子周期回购,并且您希望更轻松地进行导航,请执行以下操作

1
export FIGNORE=".svn"

现在您可以在不被.svn目录阻止的情况下使用cd


撑杆展开

带x,y,z的标准扩展:

1
2
3
$ echo foo{bar,baz,blam}
foobar foobaz fooblam
$ cp program.py{,.bak}  # very useful with cp and mv

用X..Y进行序列扩展:

1
2
3
4
$ echo {a..z}
a b c d e f g h i j k l m n o p q r s t u v w x y z
$ echo {a..f}{0..3}
a0 a1 a2 a3 b0 b1 b2 b3 c0 c1 c2 c3 d0 d1 d2 d3 e0 e1 e2 e3 f0 f1 f2 f3

使用算术:

1
2
3
if [[ $((2+1)) = $((1+2)) ]]
    then echo"still ok"
fi


不是一个特性,而是一个方向:我在commandlinefu.com上发现了许多"隐藏特性"、秘密和各种bash的有用性。许多对这些答案评价最高的答案,我都是在那个网站上学到的:)


截断文件的内容(归零文件)

1
> file

具体地说,当文件被另一个进程打开时,这对于截断日志文件非常有用,而该进程仍可能写入该文件。


for循环中用括号代替dodone

For环体通常在do...done中(只是一个例子):

1
2
3
4
for f in *;
do
    ls"$f";
done

但是我们可以使用大括号的C样式:

1
2
3
for f in *; {
    ls"$f";
}

我觉得这个比do...done好,我更喜欢这个。我还没有在任何bash文档中找到这个特性,所以这实际上是一个隐藏的特性。


另一个小的:alt+

注释当前行并将其移动到历史缓冲区。

因此,当您组装命令行时,需要发出一个临时命令,例如查找一个文件,只需点击alt+,发出另一个命令,进入历史记录,取消注释并继续。


我最近读到的CSH编程被认为是有害的,其中包含了这个惊人的宝石:

Consider the pipeline:

1
A | B | C

You want to know the status of C, well, that's easy: it's in $?, or
$status in csh. But if you want it from A, you're out of luck -- if
you're in the csh, that is. In the Bourne shell, you can get it, although
doing so is a bit tricky.
Here's something I had to do where I ran dd's
stderr into a grep -v pipe to get rid of the records in/out noise, but had
to return the dd's exit status, not the grep's:

1
2
3
4
5
6
device=/dev/rmt8
dd_noise='^[0-9]+\+[0-9]+ records (in|out)$'
exec 3>&1
status=`((dd if=$device ibs=64k 2>&1 1>&3 3>&- 4>&-; echo $? >&4) |
    egrep -v"$dd_noise" 1>&2 3>&- 4>&-) 4>&1`
exit $status;


C样式数值表达式:

1
2
3
4
5
6
7
8
9
let x="RANDOM%2**8"
echo -n"$x = 0b"
for ((i=8; i>=0; i--)); do
  let n="2**i"
  if (( (x&n) == n )); then echo -n"1"
  else echo -n"0"
  fi
done
echo""

在多个目录之间轻松移动

不是隐藏的功能,但比需要类似堆栈的导航的pushd更灵活。

1
a() { alias $1=cd\ $PWD; }

cd某处,键入a 1。稍后,只需键入1即可返回到该目录。


我经常使用的一个是!$指最后一个命令的最后一个字:

1
2
3
4
$ less foobar.txt
...
# I dont want that file any more
$ rm !$


set -o vi,以便对命令历史和当前键入的命令进行类似vi的编辑。


这些属性是我的另一个最爱。

1
2
export HISTCONTROL=erasedups
export HISTSIZE=1000

第一种方法确保bash不会多次记录命令,这将真正提高history的实用性。另一个将历史大小从默认值100扩展到1000。在我的机器上,我把这个值设置为10000。


正如其他人所提到的,ctrl r对于返回命令历史记录非常有用。但如果你想在走了一步或几步太多之后继续前进呢?这就是CtrL Sakbd的用武之地。但是,它通常被映射到xoff(中断数据流)。因为我们不再使用慢速串行终端,所以这不再有用,您可以通过以下方式关闭映射:

1
stty -ixon

在你的~/.bashrc文件中。

这也使得ctrl q可用,这通常是ctrl v(带引号的插入,允许您插入文字控制字符)的副本。我已经将ctrl q映射到菜单完成,当重复按下时,该菜单将逐步完成。我喜欢将tabkbbkbd设置为常规完成。

您可以将ctrl q设置为菜单完成,方法是将该行添加到您的~/.inputrc文件中:

1
"\C-q": menu-complete


bash具有可变的间接寻址:

1
2
3
4
$ foo=bar
$ baz=foo
$ echo ${!baz}
bar

我有一个别名r='fc-s',我发现它在某些有限的情况下非常有用。要运行最后一个命令,只需键入r并按Enter,就可以了。当然,这本身并不是很有用,因为向上箭头的作用是一样的。但是可以使用r运行带有替换的前一个命令。假设您的最后一个命令是一个长命令,编译一些文件:

1
$ gcc -c <file_name>.c <lots of options> -o <file_name>.o

现在,您希望使用相同的选项编译另一个文件,并拥有相应的.o文件:

1
$ r <file_name>=<new_file>

会做的。您不必使用向上箭头,导航到正确的位置,然后手动替换它们。这可以重复多次,因此您可以接下来执行此操作:

1
$ r <new_file>=<other_file>

当然,对于这样的事情,您有makefiles,但我希望我已经证明了别名是有用的。

我不太需要使用这个别名,但是有很多次我很高兴我有这个别名!


这里是字符串(<<<)。bash手册给出了以下描述:

The word is expanded and supplied to the command on its standard input.

例子:

1
2
$ cat<<<"$(( 10*3+1 )) nice isn't it?"
31 nice isn't it?

对基本算术使用"let"内置bash命令

1
2
3
4
A=10
let B="A * 10 + 1" # B=101
let B="B / 8"      # B=12, let does not do floating point
let B="(RANDOM % 6) + 1" # B is now a random number between 1 and 6

要执行浮点计算,可以使用"bc"命令(bash的任何部分)。

1
FP=`echo"scale=4; 10 / 3" | bc` # FP="3.3333"


用<(Cmd…)或>(Cmd…)替换进程

在每种形式中,命令执行时其输入或输出连接到一个FIFO,并在命令行上替换到该FIFO的路径:

1
2
$ echo A file to read: <(cat), a file to write to: >(cat)
A file to read: /dev/fd/63, a file to write to: /dev/fd/62

例如,在不保存中间文件的情况下比较两个网站:

1
$ diff <(curl -s http://tldp.org/LDP/abs/html/) <(curl -s http://www.redhat.com/mirrors/LDP/LDP/abs/html/)

如果您有一个将文件名作为输入的命令,但不接受"-"表示stdout,则可以对其进行欺骗:

1
2
3
4
5
6
7
$ do_thingee --log -
error: can't open log file: '-'
$ do_thingee --log >(cat)
do_thingee v0.2
initializing things
processing 4 things
done

特殊套接字文件名:/dev/tcp/host/port和/dev/udp/host/port

从日间服务器(端口13)读取:

1
2
3
$ cat < /dev/tcp/utcnist.colorado.edu/13

55786 11-08-13 03:34:21 50 0 0 172.3 UTC(NIST) *

这对于与tcpserver结合使用非常有用。

http://thesmithfam.org/blog/2006/05/23/bash-socket-programming-with-devtcp-2/提供了一个更高级的示例,如果您无法访问wget或curl:

1
2
3
4
5
$ exec 3<>/dev/tcp/www.google.com/80 # hook up to file desc 3
$ echo -e"GET / HTTP/1.1

"
>&3   # send the HTTP request
$ cat <&3                            # read the HTTP response

嵌入式命令替换:

hostname && dig +short $(hostname) && dig +short -x $(dig +short $(hostname))

此命令适用于检查邮件服务器上的RDN。P


快速历史搜索

下面给出了一个类似tcsh的历史搜索,既方便又简单。

将以下行添加到~/.inputrc/etc/inputrc中。

1
2
3
$ cat ~/.inputrc
"\e[A": history-search-backward
"\e[B": history-search-forward

您可能希望使用较少意外的组合键,如Escp。如果是这样,使用

1
2
"\ep": history-search-backward
"\en": history-search-forward

然后,只需键入前几个字母,然后按向上箭头键。它将显示以给定字母开头的最新命令。

前任:

grep型,向上箭头。它将显示类似于grep -ri myText .的内容。


在http://linuxconfig.net/manual-howto/key-combinations-in-bash.html中获取有关bash中键组合的更多信息