首页 > linux, 中级 > 高效操作Bash

高效操作Bash

2010年12月18日 ahei 发表评论 阅读评论

我们在平常工作中大量使用linux, 而使用linux的过程中操作Bash更是非常之频繁, 所以怎样高效的操作Bash是一个非常重要的问题. 下面我结合自己的经验总结一下高效操作Bash的一些技巧.

1 快捷键

1.1 注意

本文的快捷键表示中, C 表示Ctrl键, M表示Alt健. 这些快捷键中, 有一个小规律, 对字符操作一般是C开头, 对单词操作一般是M开头. 如果你用SecureCRT, 默认的话, 会输入不了Alt开头的快捷键, 因为Alt被当作菜单快捷键了, 可以点 选项 -> 回话选项, 选择tab 终端->仿真->Emacs, 把”使用Alt键作为元键”打勾. 如果你用gnome-terminal, 默认状态下也输入不了Alt开头的快捷键,也被当作菜单快捷键了,可以点 编辑 -> 键盘快捷键, 把"启用菜单快捷键"前面的勾去掉.
下面的快捷键中很多以Ctrl键开头, 很多键盘的Ctrl键并不是很好按, 可以尝试把Ctrl键和Capslock键交换.

1.2 重度推荐

  • C-r

    有时候,如果你想重新输入以前输入过的某条命令怎么办? 我见过两种做法:

    1. 不停的按向上方向键,试图找出那条命令
    2. 输入history命令,然后找到那条命令,或者grep一把history命令的输出

    其实, 你有更好的选择, 那就是按 C-r, 然后输入你想要的命令中含有的单词, 就会出现含有这个单词的命令, 如果它不是你想要的命令, 就继续按C-r, 知道出现你想要的命令为止. C-r效果:

    (reverse-i-search)`ls': ls a b c
    
  • M-.

    我经常见别人用mkdir long-long-long-name-dir后, 再输入cd, 后面跟那个长的不能再长的目录名, 这时候我就会告诉他, 其实你输入完cd后, 可以按M-., 就可以自动输入那个长的不能再长的目录名了. 其实, M-.的真正作用就是把上一条命令的最后一个参数输入到当前命令行. 非常非常之方便, 强烈推荐. 如果继续按M-., 会把上上条命令的最后一个参数拿过来. 同样, 如果你想把上一条命令第一个参数拿过来咋办呢? 用M-0 M-., 就是先输入M-0, 再输入M-.. 如果是上上条命令的第一个参数呢? 当然是M-0 M-. M-.了.

1.3 常用快捷键

  • 程序控制

    意义 快捷键
    终止当前在前台运行的程序 C-c
    挂起当前在前台运行的程序 C-z
    如果光标在行首且当前行没有输入任何字符, C-d会退出当前会话 C-d
  • 光标移动

    意义 快捷键
    向前(Forward)移动一个字符 C-f
    向后(Backward)移动一个字符 C-b
    向前移动一个单词 M-f
    向后移动一个单词 M-b
    移动光标到行首 C-a
    移动光标到行尾 C-e
  • 编辑

    意义 快捷键
    向前删一个字符 C-d
    向后删一个字符 C-h
    向前删一个单词 M-d
    向后删一个单词, 单词之间以符号分割 C-M-h
    向后删一个单词, 单词之间以空格分割 C-w
    清屏, 相当于命令clear, 有了这个快捷键, 就不用每次努力的敲clear了 C-l
    删除当前光标到行尾的字符 C-k
    删除当前光标到行首的字符 C-u
    粘贴删除环里面的第一项 C-y
    粘贴删除环里面的后面的项 M-y
    undo C-/
    取出上一条命令的最后一个参数 M-.

    对于C-M-h和C-w的区别, 看下面这个例子:

    如果当前光标前面的字符串为”abc def-ghi”, C-M-h会删掉ghi, 但是C-w会删掉”def-ghi”, 也就是说, C-M-h向后删的时候碰到非字母和数字就会停止, 但是C-w碰到空格才会停止.

    Bash下有一个删除环(kill-ring), 所有被删除的东西(用C-d删除的字符不算)都会进入这个环, C-y会粘贴环里面最近进去的项, 想要粘贴后面的项, 必须在按C-y后, 不停的按M-y, 直到出来你想要的项为止.

    有时候, 你想搜索某个文件中是否有TAB键, 你这时候会怎么做呢? 你或许会用grep, 在你输入完grep后, 你再按TAB, 这时候会出来什么? 什么都没出现! 再按? 出来:

    Display all N possibilities? (y or n)
    

    这是为何呢? 因为TAB是补全键. 那么是否是输入不了TAB吗? 不是! 按C-v后, 再按TAB即可. 同样, 想输入C-a, C-b也是同样的道理.

  • 历史命令操作

    意义 快捷键
    从历史命令列表中取下一条命令, 相当于向下方向键 C-n
    从历史命令列表中取上一条命令, 相当于向上方向键 C-p
    向后增量搜索历史命令, 非常方便, 严重推荐, 有了它, 以前输入过的很长的命令, 可以不用重复输入 C-r
    循环执行历史命令 C-o

    用C-p取出历史命令列表中某一个命令后, 按C-o可以在这条命令到历史命令列表后面的命令之间循环执行命令, 比如历史命令列表中有50条命令, 后面三项分别是命令A, 命令B, 命令C, 用C-p取出命令A后, 再按C-o就可以不停的在命令A, 命令B, 命令C中循环执行这三个命令. C-o有一个非常好用的地方, 比如用cp命令在拷贝一个大目录的时候, 你肯定很想知道当前的拷贝进度, 那么你现在该怎样做呢? 估计很多人会想到不停的输入du -sh dir去执行, 但用C-o可以非常完美的解决这个问题, 方法就是:

    1. 输入du -sh dir, 按回车执行命令
    2. C-p, C-o, 然后就可以不停的按C-o了, 会不停的执行du -sh dir这条命令

    其实上面这个问题也可以用watch命令解决:

    watch -n 1 -d du -sh dir
    

1.4 高级快捷键

意义 快捷键
从当前光标处向前搜索字符 C-]
从当前光标处向后搜索字符 C-M-]
交换当前光标下的字符和光标前面的一个字符, 交换后, 光标向后移东一个字符 C-t
交换当前光标所在单词和光标前面一个单词, 交换后, 光标向后移动一个单词 M-t
把单词首字符变成大写, 其他变成小写 M-c
把单词变成小写 M-l
把单词变成大写 M-u
删除当前光标前面所有的空白字符 M-\
向后非增量搜索历史命令 M-p
相当于TAB健 C-i
相当于回车键 C-m/C-j
在当前光标处和上一次光标处不停的移动 C-x C-x

1.5 总结

其实, 上面所说的快捷键并不是由Bash来控制的, 而是有一个叫readline的库来控制的, readline库用在很多地方, 比如gdb, mysql, 你使用gdb的时候, 是不是很奇怪, 为啥它也能用上下方向键取出前面后面的命令? 因为它用的也是readline库. 所以只要掌握了readline, 就掌握了Bash, gdb, mysql等程序里面的快捷键操作技巧. readline是一个非常非常强悍的库, 它有两种模式, 一个是Emacs模式, 另外一个是vi模式, Emacs模式非常适合在命令行下使用, 我上面说的快捷键都是针对Emacs模式来说的. readline的Emacs模式下的光标移动, 编辑等快捷键和Emacs下的快捷键也非常相近. 所以你学会了这些快捷键, 也快入门Emacs了, :) . readline也可以自定义快捷键, 它还有一套配置语法. 关于它的详细介绍, 可以man readline或者info readline, 也可以看看大牛王垠写的readline介绍.

2 历史扩展

2.1 概念

首先举个例子:
首先输入一条命令:

ls abc def ghi

再输入:

!!*:s/b/d

那么实际上执行的命令是:

adc def ghi

我来解释一下, !!表示从命令历史列表中取上一条历史命令”ls abc def ghi”, *表示选择取刚才选择的命令的所有参数, 即: “abc def ghi”, :s/b/d表示对刚才取出来的参数”abc def ghi”进行替换, 把第一个出现的b替换成d

从上面可以看出, 操作历史命令分为三步:

  • 首先从历史命令列表中选择某条命令, 被选择到的命令被称作 事件(event) (对应上面的!!)
  • 再从选择好的事件中选择一部分单词(words), 事件中的每个单词以空格分割(对应上面的*)
  • 最后对选择好的一部分单词进行修改(Modifiers)

2.2 事件指示器(Event Designators)

事件指示器用来从历史命令列表中选择一条命令, 也就是选择事件

  • !n

    选择历史命令列表中第n条命令
  • !-n

    选择倒数第n条命令
  • !!

    选择上一条命令, 相当于!-1, 和 C-p 的作用也一样
  • !string

    选择最近的以string开头的命令
  • !?string[?]

    选择最近的包含string的命令, 如果该指示器后面是换行符, 则可以不用输入结尾的”?”
  • ^string1^string2

    取上一条命令, 并把第一个出现的string1替换成string2
  • !#

    引用目前输入的所有命令, 比如输入:

    more a !#
    

    那么最终执行的命令就是:

    more a more a
    

2.3 单词指示器(Word Designators)

单词指示器用来从被选择好的事件中选择一部分单词, 单词指示器必须以冒号(:)和事件指示器分割开来, 除非单词指示器以^, $, *, -, %开头

  • 0

    选择第0个word, 也就是命令. 假如事件为”ls abc”, 那么单词指示器0选择的word即为”ls”
  • n

    选择第n个word
  • ^

    选择命令的第一个参数, 也就是第一个word, 相当于单词指示器1
  • $

    选择命令的最后一个参数
  • %

    选择最近的与 “?string?” 搜索相匹配的单词
  • x-y

    选择第x到第y个word, -y表示0-y
  • *

    选择命令的所有参数, 相当于1-$
  • x*

    x-$的缩写
  • x-

    类似x*, 不过不包含最后一个word. -选择除最后一个word外所有的words

2.4 修饰符(Modifiers)

对选择的单词进行修改, 修饰符可以出现多次, 每个修饰符要以冒号开头

  • p

    打印新命令, 但不执行
  • s/old/new/
    第一次出现的 old替换成new, 如果分隔符”/”是最后一个字符的话, 可以省略. 就像sed中一样, 分隔符”/”可以用其他字符代替, 比如s:old:new:. new中出现的&将被old代替. 如果old省略, 那么就用上一次替换用的old代替.
  • &

    重复上一次替换
  • g

    使修饰符所做的修改应用于整个选择的单词. 类似于sed中的s命令最后的g, 可配合:s和:&修饰符使用, 比如:gs/old/new则对整个事件进行替换.
  • a

    和g作用一样
  • G

    使后面的:s修饰符对每个word只替换一次

2.5 例子

  • 例一

    从别的机器的一个目录拷贝一个a.log文件, 执行:

    scp user@machine:/home/user/a/a.log .
    

    后来执行:

    ls a.log
    rm -rf a.log
    

    这时候再想拷贝一下b/b.log, 这时候就可以这样做:

    !scp:gs/a/b
    

    如果只想看看用历史扩展出来的命令, 那可以这样:

    !scp:gs/a/b/:p
    
  • 例二

    从别的机器同时拷贝a/a.log和b/b.log:

    scp user@mbchine:/home/user/a/a.log . && !#-:gs/a/b
    

    上面的!#为事件指示器, 选择前面已经输入的命令”scp user@mbchine:/home/user/a/a.log . &&”, “-”为单词指示器, 选择除最后一个word, 即”&&”外的所有words, 也就是”scp user@mbchine:/home/user/a/a.log . “, 最后的”:gs/a/b”为修饰符, 对刚才选择的words进行全局替换, 把a替换成b, 最后就成了”scp user@mbchine:/home/user/b/b.log .”, 那么最终命令也就成了”scp user@mbchine:/home/user/a/a.log . && scp user@mbchine:/home/user/b/b.log .”

2.6 总结

上面的例子都可以用前面所说的快捷键完成, 不过灵活利用历史扩展有时候还是能更高效的完成同样的事情

3 shell技巧

3.1 Here Documents

<<[-]word
here-documents
delimiter

把here-documents作为某个命令的标准输入, 例子:

grep a << EOF
asdf
qweszd
asdf
EOF

3.2 Here Strings

<<< here-strings

把word作为命令的标准输入, 例子:
grep a <<< abc

3.3 进程替换(Process Substitution)

假如我现在想比较两个目录dir1和dir2中的文件有啥不同, 我想很多人会这样做:

ls dir1 > 1
ls dir2 > 2
diff 1 2

但你试试这样:

diff <(ls dir1) <(ls dir2)

是不是也可以? 很神奇吧. 上面的这个语法<(command)就是进程替换. <(command)表示把command的输出生成一个临时文件, 并把这个文件名作为另外一个命令的参数. 对于上面的命令, 就是把”ls dir1″命令的输出生成一个临时文件, 并把临时文件名做为diff命令的第一个参数. 再举一个例子:

wget -q -O >(cat) http://baidu.com

wget命令会把下载后的文件保存到文件中去, 但是我们可以用上面的命令不让它保存到文件中去, 而是显示出来. wget的”-O”选项后本来应该是一个文件名的参数, 但是我们现在用>(cat)代替, 表示wget下载下来的内容放到一个临时文件中, 然后把这个临时文件名再传给>()里面的cat命令.
灵活运用进程替换, 将会非常的方便, 严重推荐

4 广告

呵呵, 最后做一点小广告, 这篇文章是在Emacs Org Mode下写的(本文最后一句话, HTML generated by org-mode 7.3 in emacs 23, 你看到了吗), Org Mode是Emacs内置的一个非常强悍非常强悍的Mode, 是实践GTD最好的工具, 它的功能包括但不限于: 时间管理, 做笔记, 用原始的文本格式html/pdf/latex, 画流程图等. 可以看看这几篇文章以引起你的兴趣: Emacs org mode学习笔记, Emacs中绘图 - ditaa篇, Emacs - 普通人的编辑利器.

HTML generated by org-mode 7.3 in emacs 23

分类: linux, 中级
  1. 2011年5月14日07:18 | #1

    学了好几次也没搞懂,linux还真是麻烦啊

    [回复]

    ahei 回复:

    @记忆枕, 呵呵,麻烦之后就是方便了

    [回复]

  2. zz
    2011年5月17日03:43 | #2

    博主的文章很好 学习了

    [回复]

  3. lds
    2011年6月2日04:45 | #3

    向博主学习

    [回复]

  4. 2011年10月29日06:08 | #4

    好久没有来这里了,

    [回复]

  5. xin
    2011年11月24日08:15 | #5

    博主的文章真不错,学习了

    [回复]

  6. fred
    2011年11月25日15:54 | #6

    Good :-P
    无意间发现了bash命名行的一个功能,输入命令式按 C-x C-e 会啊在/tmp 下创建一个临时文件,
    并调用编辑器进入编辑环境,这时可以创建临时批处理脚本,退出编辑器后,自动执行该脚本。

    [回复]

    ahei 回复:

    @fred, 果然阿,不错的功能

    [回复]

  7. 2012年8月28日06:24 | #7

    我最常用的快捷键是C-w、C-r和C-f/b了
    Alt键用的很少

    [回复]

  8. 2015年2月18日19:40 | #8

    Heck yeah this is exlatcy what I needed.

    [回复]

  9. 2015年3月22日17:38 | #9

    Now I feel studpi. That’s cleared it up for me

    [回复]

  10. 2015年7月24日01:12 | #10

    环氧砂浆地坪都是环氧地坪系列中的一种。环氧地坪大体可以分为这几层环氧底漆 环氧砂浆地坪 环氧中涂砂浆环氧腻子环氧面层向左转|向右转环氧地坪系列a、环 环氧地坪 氧平涂地坪(普通车间防尘、对环境要求不高的场所),涂脸的配料不当及涂抹不均匀所致。b、环氧自流平地坪(无尘车间、对厂房净化要求比较高工业生产区域),表面平整无起砂现象。c、环氧防静电地坪(对抗静电有要求的电子行业制造 涂脸的配料不当及涂抹不均匀所致 车间),施工材料由专人配料。d、环氧砂浆耐磨地坪(工厂有重负荷运行的车间、仓库、通道、地下停车场等区域),金刚砂耐磨地坪。e、抗腐蚀环氧地坪(电镀厂等有一定酸碱度的场所)f、环氧树脂防滑地坪(地下停车场之类)

    [回复]

  11. Gary
    2017年1月21日11:49 | #11

    :mrgreen: 非常感谢分享这么棒的文章!f

    [回复]

  12. Bill Z
    2017年11月5日12:44 | #12

    非常感谢, 再谢谢!

    [回复]

  1. 2011年5月17日15:30 | #1
  2. 2011年6月5日14:30 | #2
  3. 2011年10月17日09:01 | #3
  4. 2011年12月1日04:37 | #4
  5. 2012年3月23日16:56 | #5
  6. 2013年12月31日16:21 | #6
  7. 2014年1月8日13:09 | #7
  8. 2014年11月4日10:53 | #8
  9. 2015年5月15日02:47 | #9

:wink: :-| :-x :twisted: :) 8-O :( :roll: :-P :oops: :-o :mrgreen: :lol: :idea: :-D :evil: :cry: 8) :arrow: :-? :?: :!: