lujun9972 / lujun9972.github.com

151 stars 31 forks source link

UNIX超级工具 #25

Closed lujun9972 closed 5 years ago

lujun9972 commented 8 years ago

在C shell中有一个名为.logout的设置文件. 当用户退出时.logout中的命令被执行. 但是Bourne 和Korn shell中都没有退出文件. 可以使用一下方式模拟

  1. 在用户的.profile加入一行

    trap '. ~/.sh_logout;exit' 0
  2. 将退出时想运行的命令放入~/.sh_logout中

    如何防止shell意外退出

可以通过设置ignoreeof这个shell变量来解决问题:

对于C shell执行

set ignoreeof

对于bash或ksh使用

set -o ignoreeof

shell解释命令行的步骤是怎样的

  1. 对命令进行历史替换
  2. 将命令根据空格分割成词
  3. 将命令放入历史列表中
  4. 解释单引号`和双引号"
  5. shell对命令进行别名替换
  6. 输入输出的重定向(>,>>,<,|)
  7. shell将变量替换为值
  8. shell将``或$()内的命令替换为结果
  9. 文件名的通配符扩展

    如何使用echo信息到标准错误中

echo "something error" 1>&2

如何强制bash执行外置/内置命令?

  1. 如何让bash不执行shell函数,只执行内部/外部命令

    在命令前输入command即可以禁止shell函数查找

    cd ()
    {
       command cd "$@"             # 这里只会执行命令shell而不会执行cd函数
       setvars
    }
  2. 如何强制bash使用内部命令呢

    在命令前输入builtin

    builtin echo -n "this should be the builtin command echo" # 使用内置命令echo
  3. 如何强制bash使用外置命令呢?

    只需要给出外部命令的全路径即可.

    /bin/echo hi                    # 明确指明使用哪个外部命令

    或者也可以使用enable -n将某个/某几个内置bash命令无效化. enable的影响将一直持续到用户退出shell为止.

    enable -n echo ls               # 禁用内置命令echo和ls
    enable ls                       # 重新启动内置命令ls
    enable -a                       # 列出所有bash内置命令的状态

    如何禁止here Document中的变量替换和命令替换呢?

可以在EOF标识前放一个反斜杠

# 下面命令会显示$PATH
cat <<\EOF
$PATH
EOF

# 下面命令会显示$PATH的值
cat <<EOF
$PATH
EOF

shell中通配符与{}模式的区别

通配符匹配只对已经存在的文件名作扩展.

而{}模式,则可以对任意文本进行扩展,{}的用法为{扩展1,扩展2,扩展3…}. 例如

cp filename{,.bak}              # 相当于
cp filename filename.bak

vi /tmp/file{a,b,c,d,e}         # 相当于
vi /tmp/filea /tmp/fileb  /tmp/filec  /tmp/filed  /tmp/filee

ksh和bash中的变量编辑

Table 1: ksh和bash中的变量编辑操作符
操作符 解释
${variable#pattern} 删除匹配variable值头部的pattern的最短部分
${variable##pattern} 删除匹配variable值头部的pattern的最长部分
${variable%pattern} 删除匹配variable值尾部的pattern的最短部分
${variable%%pattern} 删除匹配variable值尾部的pattern的最长部分

其中pattern采取的是通配符模式,而不是正则表达式. 例如

var=/home/tmp/work/file.a.el则
echo ${var#/*/}                 # tmp/work/file.a.el
echo ${var##/*/}                # file.a.el
echo ${var%.*}                  # /home/tmp/work/file.a
echo ${var%%.*}                 # /home/tmp/work/file
echo ${var%/*}                  # /home/tmp/work可以用于取出目录值

bash中的进程替换

bash中的<(process)被用来执行process并将输出送到一个命令的命名管道中.

可以把它想象成一个文件名参数,文件的内容就是process执行的结果.

若使用的shell没有这个功能,可以用一个shell脚本来代替,该脚本执行一个命令,并将其输出保持到一个临时文件中,然后将临时文件名放到它的标准输出中.

p()
{
    eval "$@" >tmp.$$ 2>&1
    echo tmp.$$
}

shell中的历史替换机制

expr arg1 operator arg2 [operator arg3…]

返回值

如果表达式的值非0并且非空,那么expr的退出状态值为0;如果表达式的值为0或者空,则退出状态值为1;如果表达式无效,则退出状态值为2

operator操作符

yes会不断重复地输出它的参数(默认为y),使用它和head命令一起可以生成任意长度大小的文件. 例如

要生成每行8个字符(7个数字和一个换行符),共12800行的文件,则输入

yes 1234567 |head -12800 >file

其他

变量名 说明
PATH 用户的命令搜索路径,其中空记录项(::)表示当前目录
EDITOR 用户喜好的编辑器名称
PRINTER 默认的打印机名称
PWD 用户当前目录的绝对路径
HOME 用户主目录的绝对路径
SHELL 用户登录shell的绝对路径
USER/LOGNAME 用户名
TERM 终端类型名称
ENV 启动一个新ksh时需要执行的初始化文件的名称
PAGER 用户喜好的分页屏幕显示程序名称
EXINIT vi或ex编辑器初始化脚本的位置
PS1 主提示符
PS2 第二提示符
MANPATH 搜索参考手册页的路径
TZ 时间区域.这是一个位于/usr/lib/zoneinfo中的文件名
DISPLAY X Window系统所用,用来标识X应用程序将会使用的输入和输出的显示服务器
SHLVL 位于当前shell的第几层,一个subshell增加一层

如何显示其他地区现在的时间?

可以通过临时设置环境变量TZ的值,然后执行date的方式,来获得其他地区的时间. 例如

(TZ=Japanf9;date)               # 获取日本现在的时刻

环境变量和shell变量的区别

export后的shell变量就是环境变量. subshell会从shell中继承所有的环境变量,但不会继承shell变量

使用CDPATH变量为用户改变目录节省时间

执行cd foo时,shell会先尝试进入当前目录的foo目录下,若失败,则会遍历CDPATH中的各目录,并一一尝试进入其中的foo目录下

CDPATH=:~                       # 注意最开始的:,它是一个空记录项,表示当前目录,若没有这个当前目录的记录,则无论是sh还是ksh都无法cd到当前目录的子目录中!!bash不存在这个问题
cd ~/bin
cd bin                          # 若不存在~/bin/bin目录,则进入~/bin目录下

获取路径中目录信息的几种方法

  1. 使用dirname函数
  2. 使用

    组织$HOME目录

    • ~/bin存放程序和shell脚本
    • 其他类型的脚本分类存放,例如~/sedsrc存放sed脚本

      - ~/private存放私人文件,将权限设为700

      vi

    • ~/.exrc初始化vi或者ex编辑器

    启动vi或ex编辑器时,会自动执行保存在~/.exrc内的初始化命令.

    初始化命令可以是set,ab和map. 注释由双引号"开头.

    由于该文件实际上是进入vi前由ex读取的,因此exrc中的命令不应该有前置的冒号

    • 某些版本的vi在启动时不仅会加载~/.exrc,而且还会加载启动目录下的./.exrc文件

    除了.exrc文件外,还可以在其他文件中保持设置的选项,并在vi中用:so命令来读取.

    • 也可以把vi和ex的设置选项和启动过程保存在名为EXINIT的环境变量中,如果在EXINIT和.exrc文件中的设置有冲突,则EXINIT设置具有优先级.
    • 在vi的ex命令中,可以用%代表当前文件名,用#代表替换文件名.

    因此:e#的意思是切换到另一个替换文件,功能等同于C-^

    :w %.bak的意思是

    • vi中,最后一次删除的内容被存入缓冲区1中,倒数第二次的被存入缓冲区2中,以此类推共9个数字缓存区
    • ex/vi中的ex模式可以使用搜索模式来定位操作行
    操作 说明
    :/pattern/d 删除包含pattern的行
    :/pattern/+{N}d 删除包含pattern行的下面第N行
    :/pattern1/,/pattern2/d 删除包含pattern1的行与pattern2的行中的所有行
    :.,/pattern/m23 移动当前行到包含pattern的行中的所有行放到第23行后面去
    • ex/vi中的ex模式,支持g全局命令
    操作 说明
    :g/pattern/ 移动到文件中最后一个匹配pattern的行处
    :g/pattern/p 将所有符合pattern的行显示出来
    :g!/pattern/nu 显示所有不符合pattern的行,同时显示行号
    :60,124g/pattern/p 显示60-124行直接所有包含pattern的行
    :g/^WARNING/s/\\/NOT/ 将所有以WARNING开头的行中的not替换为NOT
    :g/^START$/,/^END$/d 删除所有START和END内的内容
    • 可以使用ex行定位命令和w命令组合起来,以保存部分文件

    :.,600w newfile 把当前行到第600行的内容写入newfile中

    • 使用>>和w命令一起,可以把内容添加到一个现有的文件中

    :.,600w >>newfile 可以把当前行到600行的内容添加到newfile中,而不覆盖原newfile的内容

    • 使用:s替换时,可以使用\U表示将后面的模式替换成大写形势,用&指代签名的搜索式
    • 在ex中,|是一个命令分隔符,作用类似UNIX命令行中的分号;
    • 使用vi -r 文件名 可以恢复被杀掉的文件,使用vi -r会列出所有可以恢复的文件列表
    • vi支持符合搜索

    象/Los Alamos/;/treasure/表示找到出现在Los Alamos后的treasure处,即使这两个短语可能不在同一行.

    类似于/Los Alamos然后再/treasure. 不同的是,它可以使用n命令来重复搜索

    • vi支持:tag命令来搜索ctags命令创建的tag文件.

    可以通过设置tags的属性来设置多个tags文件.

    • vi支持使用:ab定义缩写词

    :ab 缩写 全称

    还可以用:unab 缩写来取消缩写词定义.

    :ab 则会列出当前已定义的缩写词

    需要特别说明的是: ab定义的缩写词,对ex模式也生效,事实上,对ex模式下的命令,用缩写词比用键映射更好.

    • 默认情况下,vi会把用户正在编辑的文件存放在临时文件目录下,若编辑的一个大文本,则可能发生临时文件目录溢出的情况,这时需要指定其他目录存放,方法为

    :set directory=/some/place/new

    • 可以使用{}命令快速切换到上一/下一个段落处.

      vi的命令模式映射:

使用map智能在命令模式下定义宏.

使用map!的作用类似map,但是map!在文本输入模式下起作用,它的功能类似ab

vi的宏定义

vi虽然不支持使用q来定义宏,但是支持用@来执行宏.

设置终端

登录时设置终端类型

通过输入stty erase {控制字符} 可以将{控制字符}设定为删除键.

stty让用户用两个字符的组合char来代表一个控制键. 其中^就是键^本身,而{char}是任意的单个字符. 可能需要在{char}前放入一个\,以防止shell将其解释为一个通配符

例如

stty erase ^h
stty erase ^\?

stty可以改变的功能包括:

Table 2: 用stty设置的键
字符 功能
erase 删除先前的字符
kill 删除整行
werase 删除先前的字
intr 终止当前作业
quit 终止当前作业,生成一个core文件
susp 停止当前作用
rprnt 重新显示当前行

用stty -a会显示用户当前所有终端的设置. werase和rprnt字符有些UNIX版本未实现.

从哪里寻找可能可以使用的终端类型

可以通过搜索/etc/termcap文件内容或者通过列出在/usr/lib/terminfo目录结构中的文件名来寻找终端名,以方便地设置TERM

文本处理

分割文本

按行数分割

若希望并排粘贴N个文件的内容,则可以使用paste命令

paste <(ls) <(ls -r)
# my-byte-split.sh  my-line-split.sh
# my-line-split.sh  my-byte-split.sh

合并的数据流默认情况下使用TAB分割,但是可以用-d选项来指定分隔符。

匹配连接两个文本的内容

join会在文件中搜索某些列,找到相互匹配的行之后,它会在该列的位置上把两列文本粘帖在一起

默认join会以第一列的内容进行匹配,也可以使用-1 FIELD和-2 FIELD来指定file1和file2的FIELD列

uniq用来删除以排序的文件中相邻文本行的重复内容

需要注意的是:

uniq file1 file2

的意思是用file1的唯一行代替file2的内容

使用sort对文本进行排序

case i in
    ?)                          # 匹配只有1个字符的字符串
            ;;
    ?*)                         # 匹配有一个或多个字符的字符串
            ;;
    [yY]|[yY][eE][sS])          # 匹配y,Y或者YES,Yes,YeS等
            ;;
    /*/*[0-9])                  # 匹配以/开始并且至少再包含一个/的一数字结尾的文件路径名,例如/xxx/yyy/somedir/file2
            ;;
    'what now?')                # 匹配模式what now?。引号告诉shell按字面意思解释
            ;;
    "$msgs")                    # 匹配$msgs变量的内容。双引号允许shell替换变量的值
            ;;
    *)                          # 匹配所有的值,起默认值的作用

trap捕获信号量

trap "command1;command2…" sign1 sign2…

捕获到sign1,sign2…等信号后,执行command1;command2

Table 3: 一些用于trap命令的UNIX信号编号
信号编号 信号名称 解释
0 EXIT 退出命令
1 HUP 当会话断开链接时
2 INT 中断,C-c
3 QUIT 退出,C-\\
15 TERM 来自kill

使用getopt/getopts处理脚本的参数解析

使用set命令重新初始化脚本的参数

使用set 新参数1 新参数2…能够初始化脚本的参数。

#set_test.sh
echo before set : $@
set 1 2
echo after set : $@
# set_test.sh 3 4
# before set : 3 4
# after set : 1 2

但要注意的是,若新参数以-开头的,则shell会把它看出是自己的选项。

使用jot命令产生数组

jot 要产生多少个参数 [起始参数值 结束参数值]

使用exec可以重定向shell脚本的IO

exec command会执行comand来代替当前shell,它常常用于shell脚本的最后一个命令.

但exec也可以用来重定向当前shell脚本的IO,例如

exec < formfile

上面命令使得当前shell中所有命令的标准输入来自于文件formfile

:操作符

:操作符会计算它的参数值,并返回0退出状态,它可以用来

!的原理实际是将脚本名作为参数拼接到#!后面的程序后面来运行该脚本的. 例如

#! /bin/bash
#假设该脚本名称为test.sh
commands...

则在直接执行该脚本时,内核实际上执行的是

/bin/bash test.sh

这也是为什么用awk作为#!行的命令解释程序时,需要加-f的原因,因为-f表示用从文件中读取脚本.

#! /usr/bin/awk -f
{print $2}

因此使用#!/bin/cp可以制作自复制脚本. 将它放到名为zap的文件中,运行zap zup,则会有一份zup的自拷贝

另外,需要注意,内核不会去搜索PATH路径,因此#!中的解释程序必须使用绝对路径.

如何将捕获到的信号传递给子进程?

使用trap : sign1 sign2…

会忽略信号,向子进程传递信号

参数替换操作符

操作符 说明
${var:-default} 如果没有设置var或者var为空,那么使用default代替
${var:=default} 如果没有设置var或者var为空,那么将它设为默认值并使用该值
${var:+instead} 如果已经设置var并且var不为空,那么使用instead,否则什么都不用(空字符串)
${var:?message} 如果已经设置var并且var不为空,那么使用它的值,否则打印message并退出shell,若message为空,则显示一条默认消息

为了保护密码而关闭回显

使用stty -echo就能关闭回显,这样用户的输入就不会显示在屏幕上了

echo "enter the password"
stty -echo
read pwd
stty echo

sh<file与sh file有时候那么区别?

sh<file若file中有无法进行read操作

创建锁文件,防止多个进程同时操作

掩码只会在文件存在时起作用.如果文件尚不存在,掩码就不会应用于它. 基于该特性,可以创建一个锁文件

name = $(basename $0)
LOCKFILE=/tmp.lock.$name
until (umask 222;echo $$ >$LOCKFILE) 2>/dev/null # 若已 存在锁文件,则该操作失败,否则成功
do
    sleep 5
done

rm -f $LOCKFILE

事实上,可以通过如下命令来创建模式为000的文件

(umask 666;echo hi >afile)
chenyangguang commented 5 years ago

你为何如此优秀?