技巧

SHLVL变量可查看当前shell的层次

用-替换标准输入

-经常用在管道指令中,用来补足某些指令需要的参数。如:

`` example
echo 'joe@sample.edu.cn' | cat - email.lst | sort


会把'joe@sample.edu.cn'和email.lst合并,然后排序输出。

<h2 id="用script记录命令的执行过程">用script记录命令的执行过程</h2>

<h2 id="统计每种TCP连接状态的连接数">统计每种TCP连接状态的连接数</h2>

example

netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
netstat -an | awk '/tcp/ {print $6}' | sort | uniq -c


<h2 id="计算日期的巧妙公式">计算日期的巧妙公式</h2>

example

(Year*365)+(Year/4)-(Year/100)+(Year/400)+(Month*306001/10000)+(Day)


<h2 id="隐藏文件">隐藏文件</h2>

在Bash中的'.\*'意味着什么? Bash中的filename expansion or
globbing是这么处理的:只要在发现字符串中有 **, ?, \[
这几个符号,这个字符串就会被视为pattern。因此'.**'意味着的有以'.'开头的文件或文件夹。不过,'.\*'会包含'.'和'..',很多时候这并不是使用者想要的结果。如果要显示所有隐藏文件,而又不包含当前目录与上一级目录,就得写成下面这样:

example

ls -d .[!.]*


注意:'.'在Bash中并没有一般正则表达式中匹配任意字符的含义,Bash中'.'就是'.',是普通符号。上面的命令中用'.\[^.\]\*'也是可以的,但用'!'更为通用。
有一种比较简单的办法可以来验证bash对pattern的扩展:

example

echo .*


有一种trick,在ls命令被破坏时使用 echo \* 参看: Bash Cookbook 1.5
Showing All Hidden (dot) Files in the Current Directory

<h3 id="拷贝/etc/skel目录下的隐藏文件">拷贝/etc/skel目录下的隐藏文件</h3>

由于/etc/skel目录下的文件均为隐藏文件,用下面的命令是无法实现拷贝的:

example

cp -a /etc/skel/* /home/user/


因为Bash在/etc/skel/下找不到普通文件,'**'号无法扩展,就是提示找不到文件'*etc/skel***'。
用下面的'.\*'也有问题,一般会有警告:

example

cp -a /etc/skel/.* /home/user/


正确做法是下面的命令:

example

cp -a /etc/skel/. /home/user/
cp /etc/skel/.[!.]* /home/user/


<h1 id="Bash Process Substitution">Bash Process Substitution</h1>

Process Substitution在info文档中的位置为:Basic Shell Features \> Shell
Expansions \> Process Substitution 。 可以看出process
substitution归到了shell expansion之下,而不是重定向。process
substitution有两种形式:\<(LIST)和\>(LIST)。process
LIST的输入或输出会连接到FIFO或/dev/fd下的某个文件,expansion的结果就是将\<(LIST)或\>(LIST)替换成对应的文件名。由此可以看出process
substitution可以出现在任何以文件名为参数的位置。\>(LIST)会把原来写到某个文件的内容作为LIST的输入。\<(LIST)会把LIST的输出作为当前命令需要访问的文件。
一次读取一行数据放入数组中,可用process substitution实现如下:

example

read -a arr < <(echo 123 45 97 101 88)


<h1 id="pipeline">pipeline</h1>

pipeline中每个命令都是在subshell中运行的,因此下面的代码是不会输出东西的:

example

echo "2012 02 05" | read year month day
echo "year=$year,month=$month,day=$day"


但是如果把read包在while语句中,输出显示三个变量的值正确获取了:

example

echo "2012 02 05" | while read year month day ; do echo "year=$year,month=$month,day=$day" ; done


究其原因,两个例子中read方法的调用都没有问题,只不过,第一个例子中的三个变量只能在read所在的subshell中有效,从新回到主shell中,这几个变量已经不可见了。一种解决方法是使用process
substitution:

example

read year month day < <(echo "2012 02 05")


参考:http://www.fvue.nl/wiki/Bash:<sub>Piped</sub>\_%60while-read%27<sub>loopstartssubshell</sub>

<h1 id="debug">debug</h1>

  1. 检查语法
example

Bash -v test.sh


  1. 不执行,仅查看程序代码
example

Bash -n test.sh


  1. 追踪脚本执行
example

Bash -x test.sh


<h1 id="启动配置文件">启动配置文件</h1>

<h2 id="命令行Login">命令行Login</h2>

首先读入
*etc/profile,这是对所有用户都有效的配置;然后依次寻找下面三个文件,这是针对当前用户的配置。
~*.bash<sub>profile</sub> ~/.bash<sub>login</sub> ~/.profile
需要注意的是,这三个文件只要有一个存在,就不再读入后面的文件了。比如,要是
~/.bash<sub>profile</sub> 存在,就不会再读入后面两个文件了。

<h2 id="注销(Logout)">注销(Logout)</h2>

.bash<sub>logout</sub>

<h2 id="non-login shell">non-login shell</h2>

non-login
shell与登录时的shell并不相同,它不读取/etc/profile和.profile等文件。non-login
shell的重要性,不仅在于它是用户最常接触的那个shell,还在于它会读入用户自己的bash配置文件
~/.bashrc。大多数时候,我们对于bash的定制,都是写在这个文件里面的。
*etc/bash.bashrc ~*.bashrc 如果不进入non-login
shell,是不是就不执行.bashrc了呢?其实像Debian这样的发行版会在.profile中去调用.bashrc,因此,写在.bashrc上的个人配置在登录后还是会生效的。
Bash的设置之所以如此繁琐,是由于历史原因造成的。早期的时候,计算机运行速度很慢,载入配置文件需要很长时间,Bash的作者只好把配置文件分成了几个部分,阶段性载入。系统的通用设置放在
/etc/profile,用户个人的、需要被所有子进程继承的设置放在.profile,不需要被继承的设置放在.bashrc。

<h2 id="执行Script(使用#!/bin/bash)">执行Script(使用#!/bin/bash)</h2>

不调用.bash<sub>profile或</sub>.bashrc,但会检查BASH<sub>ENV的内容</sub>,如果非空,则会执行BASH<sub>ENV所指定的启动文件</sub>。

<h2 id="执行Script(使用#!/bin/sh)">执行Script(使用#!/bin/sh)</h2>

不调用任何启动文件,没有其他检查环境变量的操作。

<h1 id="用空语句写死循环">用空语句写死循环</h1>

bash中的“:”表示空语句,结果总是成功,正好可以用来写死循环。

shell

while :
do

do something

done


<h1 id="编译Bash4.2时出现job<sub>control未定义错误</sub>">编译Bash4.2时出现job<sub>control未定义错误</sub></h1>

<http://lists.gnu.org/archive/html/bug-bash/2011-09/msg00039.html>
编译时会出现如下错误信息:

example

execute_cmd.c: In function ‘execute_pipeline’:
execute_cmd.c:2205:23: error: ‘job_control’ undeclared (first use in this function)


代码中使用了一个没有定义过的变量job<sub>control</sub>,参考上面的链接修改:

example

if (lastpipe_opt && job_control == 0 && asynchronous == 0 && pipe_out == NO_PIPE && prev > 0)


改成:

example

#if defined (JOB_CONTROL)
if (lastpipe_opt && job_control == 0 && asynchronous == 0 && pipe_out == NO_PIPE && prev > 0)
#else
if (lastpipe_opt && asynchronous == 0 && pipe_out == NO_PIPE && prev > 0)
#endif /* JOB_CONTROL */

``

zsh

bullet-train

<https://github.com/caiogondim/bullet-train.zsh>

热键绑定

zsh 里面使用 bindkey 命令可以设置一系列热键,用来运行某一个 zsh
内部命令或者某个 shell
命令,谁规定终端只能敲字母呢?我们还可以按热键,比如从网上下载了一个 tar
包解开后要稍微浏览一下里面的内容,用的最多的两条命令是啥呢?第一条是 ls
命令,每到一个子目录都要先按一下,还有就是 cd .. 对吧,经过配置:bindkey
-s '' 'cd ..' \# 按下ALT+O 就执行 cd .. 命令 bindkey -s ';' 'ls -l' \#
按下 ALT+; 就执行 ls -l 命令
你还可以设置一键打开编辑器,或者一键帮你输入某常用命令的一部分。除了这些命令外,日常命令编写也可以加强一下:bindkey
'\[1;3D' backward-word \# ALT+左键:向后跳一个单词 bindkey '\[1;3C'
forward-word \# ALT+右键:前跳一个单词 bindkey '\[1;3A'
beginning-of-line \# ALT+上键:跳到行首 bindkey '\[1;3B' end-of-line \#
ALT+下键:调到行尾
敲命令时经常需要对已有命令进行修改,默认一个字符一个字符的跳太慢了,这样设置以后基于单词的跳转快速很多,配合其他一些快捷键,修改命令事半功倍。终端下从
v220t 到 xterm 规范里,按下 alt+x 会先发送一个8位 ASCII 码 27,即
ESC键的扫描吗,然后跟着 x
这个字符,也等价于快速(比如100毫秒内)前后按下 ESC 和 x。