巴斯的隐藏特征

Shell 脚本通常用作粘合剂,用于自动化和简单的一次性任务。你最喜欢的 Bash shell/脚本语言的“隐藏”特性是什么?

  • 每个答案只有一个特征
  • 给出一个特性的例子和简短描述,而不仅仅是文档的链接
  • 使用粗体标题作为第一行标记特性

参见:

41255 次浏览

使用中缀布尔运算符

考虑一个简单的条件:

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

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

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

使用算术:

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

我最近读了 认为 Csh 编程有害,其中包含了一个令人震惊的宝石:

考虑一下管道:

A | B | C

你想知道 C 的状态,这很简单: 它在 $? ,或者 $status in csh. But if you want it from A,you’re out of luck —— if 在 csh 中,也就是说,在 Bourne shell 中,您可以得到它 这样做有点棘手。 这是我做 DD 的时候不得不做的事情 Stderr 放到 grep-v 管道中以消除输入/输出噪声的记录,但是有 返回 dd 的退出状态,而不是 grep 的:

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;

特殊随机变量:

if [[ $(($RANDOM % 6)) = 0 ]]
then echo "BANG"
else
echo "Try again"
fi

我喜欢-x 特性,它允许查看脚本中发生了什么。

bash -x script.sh

几乎所有在扩展部分下列出的手册

特别是参数展开:

$ 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

阵列:

#!/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 脚本)的更多细节。

下面是我的两个最爱:

要检查真正执行脚本的 w/o 语法,请使用:

bash -n script.sh

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

cd -

返回历史命令和参数

可以使用 !操作符有选择地访问以前的命令和参数。当您使用较长的路径时,它非常有用。

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

可以使用以前的命令,!<n>n,是 history中命令的索引,负数从历史上最后一个命令开始向后计数。

ls -l foo bar
touch foo bar
!-2

可以在 !:<n>中使用前面的参数,0是命令,> = 1是参数。

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

你可以把它们和 !<n>:<m>结合起来

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

还可以使用参数范围 !<n>:<x>-<y>

touch boo far
ls -l !:1-2

其他 !特殊修饰词包括:

  • 所有参数的 *

    ls -l foo bar
    ls !*
    
  • ^ for the first argument (!:1 == !^)

  • $ for the last argument

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

C 风格的数字表达式:

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 ""

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

> file

特别是,当文件由另一个进程打开时,这对于截断日志文件非常有用,因为这个进程仍然可以写入文件。

正则表达式处理

最近发布的 bash 版本具有正则表达式匹配功能,因此您可以这样做:

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

其中 REGEX 是 man re _ format 描述的格式的原始正则表达式。

任何括号内的匹配项都存储在 BASH _ REMATCH 数组中,从元素1开始(元素0是完整的匹配字符串) ,因此您也可以使用它进行正则表达式驱动的解析。

嵌入式指令替代:

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

此命令适用于检查邮件服务器上的 RDNS

插入前一行的最终参数

alt-.是有史以来最有用的组合键,试试看,出于某种原因没有人知道这一个。

按一次又一次选择旧的最后参数。

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

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

disown -h <pid>

nohup不同,您可以在已经运行的进程上运行 disown

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

不要忘记背景化你的工作,否则当你注销的时候它将被杀死。

快速和肮脏地纠正拼写错误(特别适用于使用命令历史和滚动浏览历史将是可怕的长命令超过缓慢的连接) :

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

也可以尝试 !:s/old/new,在前一个命令中用 new 替换 old。

如果要替换许多匹配项,可以使用 !:gs/old/new进行全局替换。

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

!-2:s/old/new

在倒数第二个命令中用 new(一次)替换 old

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

set completion-ignore-case on

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

在“ PROMPT _ COMMAND”env 变量中设置一个命令,它将在每次提示之前自动运行。 例如:

[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 中,然后坐下来观察混乱的发生。

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

几秒钟

每次这个参数是 引用,秒数 因为将返回 shell 调用。 如果一个值被分配给 SECONDS, 返回的值 引用是秒数 因为赋值加上价值 如果未设置 SECONDS,则 失去了它的特性,即使 它随后被重置。

使用“ let”内置 bash 命令进行基本算术运算

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 的非部分)。

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

这些房产也是我的最爱之一。

export HISTCONTROL=erasedups
export HISTSIZE=1000

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

Ctrlx Ctrle

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

使用 vi 作为编辑器:

export VISUAL=vi

在多个目录之间轻松移动

这不是一个隐藏的特性,但是比按压更加灵活,后者需要类似于堆栈的导航。

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

键入 a 1,稍后键入 1将返回到该目录。

我的最爱:

sudo !!

用 sudo 重新运行前面的命令。

我经常使用的一个方法是! $来指最后一个命令的最后一个单词:

$ less foobar.txt
...
# I dont want that file any more
$ rm !$

Bash man页面中的魔法组合键:

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

  • Ctrl + tAlt + t将光标前的字符和单词与当前字符和单词互换,然后将光标向前移动。

  • Alt + uAlt + l将当前单词(从光标到末尾)转换为大写和小写。

    提示: Alt + ,然后按以下任一命令转换当前单词的 开始


额外的 man小贴士:

  • 在查看 man页时,使用 /在页中搜索文本。使用 n跳转到下一场比赛,或者使用 N跳转到上一场比赛。

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

    O 不要键入 /history expansion来查找那个部分,尝试使用 /^history,使用插入符号(^)来只查找带有“ history”的 开始

    O 尝试使用带有几个前导空格的 /   read来搜索那个内置命令。内置总是缩进的 man页。

更多魔法密钥组合:

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

  • 如果你输入的单词没有歧义,那么 Tab就完成了它。

  • Tab列出了到目前为止你输入的单词的所有完成项。

  • Alt + * 插入物所有可能的补全,这特别有帮助,例如,如果您刚刚使用通配符输入了一个潜在的破坏性命令:

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

  • Ctrl + Alt + e在当前行上执行别名、历史记录和 shell 扩展。换句话说,当前行将由 shell 处理时重新显示:

    Ctrl Alt + e
    ls -N --color=tty -T 0 /home/cramey

正如其他人所提到的,Ctrl-r非常适合回溯您的命令历史。但是如果你已经走了太多的一步或者几步之后,你还想继续前进,那该怎么办呢?这就是 Ctrl-s派上用场的地方。但是,它通常映射到 XOFF (中断数据流)。因为我们没有使用慢速串行终端,所以这个功能已经不再有用了,你可以关闭这个映射:

stty -ixon

在你的 ~/.bashrc文件中。

这也使得 Ctrl-q可用,它通常是 Ctrl-v的副本(引号插入允许您插入文字控制字符)。我有 Ctrl-q映射到菜单-完成的步骤,通过完成时,按重复。我喜欢离开 Tab设置为定期完成。

您可以设置 Ctrl-q菜单完成通过添加这一行到您的 ~/.inputrc文件:

"\C-q": menu-complete

Bash 具有可变的间接性:

$ foo=bar
$ baz=foo
$ echo ${!baz}
bar

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

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

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

$ r <file_name>=<new_file>

会做的。您不必使用向上箭头,导航到正确的地方,然后每个手动替换它们。这个过程可以重复很多次,所以接下来你可以这样做:

$ r <new_file>=<other_file>

当然,对于这样的东西,您已经有了 makefile,但是我希望我已经展示了别名是有用的。

我并不是很需要使用这个化名,但是有时候我很高兴我有这个化名!

export TMOUT=$((15*60))

在空闲时间15分钟后终止 bash,设置为0禁用。我通常把这个放到 ~/。我的根帐户的 bashrc。这是方便管理您的盒子,您可能会忘记注销之前,步行离开终端。

实际上不是一个特性,而是一个方向: 我在 Commandlinefu.com中发现了许多“隐藏特性”、秘密和各种 bash 有用性。许多评分最高的答案,我都是在那个网站上学到的:)

以便对命令历史记录和当前类型的命令进行类似 vi 的编辑。

撤销

撤消键入动作。

杀死/美国佬

Any delete operation C-w (delete previous word), C-k (delete to end of line), C-u (delete to start of line) etc... copies it's deleted text to the kill ring, you can paste the last kill with: C-y and cycle through (and paste from) the ring of deleted items with Alt-y

通过设置 FIGNORE变量,您可以在选项卡完成时忽略某些文件。

例如,如果你有一个 subverion 回购和你想导航更容易做

export FIGNORE=".svn"

现在你可以不被 .svn目录阻塞 cd

另一个小问题: Alt + #

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

所以当你在组装一个命令行的时候,你需要发出一个临时的命令,比如说,查找一个文件,你只需要点击 alt + # ,发出另一个命令,在历史中显示,取消注释,然后继续。

这里是字符串 (<<<):

该单词在其标准输入上展开并提供给命令。

例如:

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

进程替换为 < (cmd...)或 > (cmd...)

在每种形式中,cmd 的输入或输出都与 FIFO 挂钩,通往 FIFO 的路径在命令行中被替换:

$ 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

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

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

如果您有一个以文件名作为输入的命令,但不接受“-”表示 stdout,您可以欺骗它:

$ 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)读取:

$ cat < /dev/tcp/utcnist.colorado.edu/13


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

这在与 Tcpserver结合使用时非常有用。

如果你不能访问 wget 或 curl,那么来自 http://thesmithfam.org/blog/2006/05/23/bash-socket-programming-with-devtcp-2/的一个更高级的例子:

$ exec 3<>/dev/tcp/www.google.com/80 # hook up to file desc 3
$ echo -e "GET / HTTP/1.1\n\n" >&3   # send the HTTP request
$ cat <&3                            # read the HTTP response

支撑扩张

{ x,y,z }的标准展开式:

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

{ x. . y }的序列展开:

$ 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

快速历史搜索

下面给出了一个 tcsh like 历史搜索,它非常方便和容易。

将以下代码行添加到 ~/.inputrc/etc/inputrc

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

您可能希望使用一个不那么偶然的组合键,如 Esc + p。如果是这种情况,请使用

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

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

例如:

类型 grep,向上箭头。 它会显示类似 grep -ri myText .的东西

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

在 for 循环中用大括号代替 dodone

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

for f in *;
do
ls "$f";
done

但是我们可以使用大括号的 C 风格:

for f in *; {
ls "$f";
}

我觉得这个看起来比 do...done好,我更喜欢这个。我还没有在任何 Bash 文档中找到这个特性,所以这确实是一个隐藏特性。