SHELL脚本编程以及日常使用技巧

2020-08-14  本文已影响0人  clstou

基础

从一个命令行开始

grep --context=2 -niE 'printf|log' -- *.{h,cpp} > /tmp/result 2>/dev/null

该命令行使用grep命令在当前目录下的后缀为.h以及.cpp的文件中,查找包含printf或者log的行, 并将结果保存到文件/tmp/result。包括了命令行的几个典型元素。这里从左到右依次说明:

上面的命令行中,需要说明的几点是:

SHELL命令解析流程

st=>start: 将命令分隔成tokens
cond1=>condition: 检查第一个token是否为关键字
cond2=>condition: 检查第一个token
operr=>operation: 语法错误
op1=>operation: 波浪号展开
op2=>operation: 变量替换
op3=>operation: 命令替换
op4=>operation: 算数表达式替换
op5=>operation: 展开的文本进行单词分隔
op6=>operation: 通配符展开
op7=>operation: 命令查找:特殊内建命令、函数、内建命令、可执行文件
op8=>operation: 完成IO重定向以及其他同类型事项后
cond3=>condition: 执行命令
e=>end

st->cond1
cond1(yes)->cond2
cond1(no)->operr
cond2(yes)->op1
op1->op2->op3->op4
op4->op5->op6->op7
op7->op8->cond3
cond3(yes, left)->st

注: 执行命令这里,如果执行的命令是eval的话,会重新从开始走一遍扫描流程。因为Markdown流程图这里不好画,所以就暂时留空了。具体可以参考Shell脚本学习指南关于命令解析部分。
这里引用一张网上的图:
[站外图片上传中...(image-31c13e-1597399378750)]

常见的命令

文本处理

大部分Linux命令,特别是文本处理命令都是从标准输入读入数据,并输出到标准输出。下面是一些常用且应该熟练掌握的命令。

其他常用命令

学会看手册

命令有很多,而且选项也很多,单靠记忆是很难全部记住的。所以要学会RTFM。下面是看手册也的一些基本要点:

       The standard sections of the manual include:

       1      User Commands

       2      System Calls

       3      C Library Functions

       4      Devices and Special Files

       5      File Formats and Conventions

       6      Games et. Al.

       7      Miscellanea

       8      System Administration tools and Daemons

pgrep [-flvx] [-d delimiter] [-n|-o] [-P ppid,...] [-g pgrp,...]
[-s sid,...] [-u euid,...] [-U uid,...] [-G gid,...]
[-t term,...] pattern

其中`[]`包含的内容均为可选内容,不在`[]`内的内容则为必选内容。可有可无;`|`则表示或;`...`则表示多个。斜体字必须用适当的值替换, 例如上面的*pattern*。

脚本初步

原则

编码规范

可以参考google shell脚本编程规范
这里特别提两点,变量名使用小写加小划线,环境变量以及全局变量放开头,并用大写加下划线;
shell脚本不需要用.sh作为结尾,除非是作为库函数使用,而且作为库函数使用时,不需要有执行权限。之所以不需要以.sh结尾的原因在于,文件后缀对于Linux下的可执行文件没有特殊意义,另外,脚本有可能使用其他语言重写,调用方只需要知道脚本的功能,没有必要知道脚本的编写语言。

脚本的执行

Linux在执行命令的时候,首先会判断命令是否是一个可执行的二进制文件,如果是,则直接执行。如果不是且文件以#!开头,则把该命令作为一个脚本执行(解释器文件)。并以#!后面指定的命令作为解释器。流程如下:

st=>start: 执行命令foo arg1
cond=>condition: 是否为可执行二进制
op1=>operation: 执行二进制命令(foo arg1)
cond2=>condition: 是否为解释器文件
op2=>operation: 执行解释器文件(/path/to/cmd -pf /path/to/foo arg1)
op3=>operation: 报错
e=>end: 结束

st->cond
cond(yes)->op1
op1->e
cond(no, right)->cond2
cond2(yes)->op2->e
cond2(no, right)->op3->e

这里假设脚本文件路径为/path/to/foo,脚本内容为:

#!/path/to/cmd -pf
script content comes here...

变量

bash支持定义readonly变量,整型变量等。

条件判断

bash支持if条件判断(条件测试包括文件是否存在,命令是否执行成功等, 具体可以参考test(1))以及switch case条件判断。(help case)

循环

bash支持for以及while循环语句。具体可以参考help for以及help while

主要shell编程参考

以上只是简单介绍,shell脚本编程完整的信息可以参考bash(1)手册页以及 Shell脚本学习指南

日常使用

下面是我个人针对平常日常操作做的一些总结,比较主观,大家可以参考一下。以下说明如无特殊说明,均针对bash

一些不好的习惯

一些好的习惯

一些容易混淆的概念

正则与文件匹配模式

正则与平常在shell命令行中使用的文件匹配模式是两种不同的东西。正则表达式有多种实现,日常日用最多的是BRE(basic RE)ERE(extend RE),除此之外还有功能强大的PRE(Perl RE)。文本处理命令一般都使用正则。而文件匹配模式一般仅在在shell命令行解析中使用。(case中使用的也是文件匹配模式)。具体可以参考手册页man 7 glob。这里举一个例子:

grep -E 'log_(debug|info)\(' *.{h,cpp}
grep '[a-z]*' [e-f]*.h

log_(debug|info)\(使用的ERE的语法,支持成组以及或等高级功能,这里匹配log_debug(或者log_info([a-z]*使用的是BRE,支持一般的正则匹配, 这里匹配任意个小写字母(注意,0个也是任意个)。*.{h,cpp}以及[e-f]*.h则为文件匹配模式,分别匹配当前目录下以.h.cpp结尾的文件,以及ef开头并加上任意字符以及以.h结尾的文件。

环境变量

设置环境变量仅对当前进程以及它的子进程有效。

一些常用的技巧

以下罗列的一些技巧在参考的书籍中基本都有提及。如果本身对shell编程了解不多并且有时间的话,系统的查阅书籍。这样可以花比较少的时间,系统以及全面掌握。

Bash快捷键

在输入命令行的时候,bash支持vi以及emacs两个模式。一般emacs模式更方便。这里罗列一下常用的快捷键, 完整版快捷键说明可以参考readline-emacs-editing-mode-cheat-sheet.pdf或者使用bash内置命令bind -p查看:

======================== Keyboard Shortcut Summary ========================

.--------------.-------------------.----------------------------------------.
|              |                   |                                        |
| Shortcut     | Function          | Description                            |
|              |                   |                                        |
'--------------'-------------------'----------------------------------------'
| Commands for Moving:                                                      |
'--------------.-------------------.----------------------------------------'
| C-a          | beginning-of-line | Move to the beginning of line.         |
'--------------+-------------------+----------------------------------------'
| C-e          | end-of-line       | Move to the end of line.               |
'--------------+-------------------+----------------------------------------'
| C-f          | forward-char      | Move forward a character.              |
'--------------+-------------------+----------------------------------------'
| C-b          | backward-char     | Move back a character.                 |
'--------------+-------------------+----------------------------------------'
| M-f          | forward-word      | Move forward a word.                   |
'--------------+-------------------+----------------------------------------'
| M-b          | backward-word     | Move backward a word.                  |
'--------------+-------------------+----------------------------------------'
| C-l          | clear-screen      | Clear the screen leaving the current   |
|              |                   | line at the top of the screen.         |
'--------------'-------------------'----------------------------------------'
| Commands for Changing Text:                                               |
'--------------.-------------------.----------------------------------------'
| Rubout       | backward-delete-  | Delete one character backward.         |
|              | char              |                                        |
'--------------+-------------------+----------------------------------------'
| C-q or C-v   | quoted-insert     | Quoted insert.                         |
'--------------+-------------------+----------------------------------------'
| M-TAB or     | tab-insert        | Insert a tab character.                |
| M-C-i        |                   |                                        |
'--------------+-------------------+----------------------------------------'
| a, b, A, 1,  | self-insert       | Insert the character typed.            |
| ...          |                   |                                        |
'--------------+-------------------+----------------------------------------'
| C-t          | transpose-chars   | Exchange the char before cursor with   |
|              |                   | the character at cursor.               |
'--------------+-------------------+----------------------------------------'
| M-t          | transpose-words   | Exchange the word before cursor with   |
|              |                   | the word at cursor.                    |
'--------------+-------------------+----------------------------------------'
| M-u          | upcase-word       | Uppercase the current word.            |
'--------------+-------------------+----------------------------------------'
| M-l          | downcase-word     | Lowercase the current word.            |
'--------------+-------------------+----------------------------------------'
| M-c          | capitalize-word   | Capitalize the current word.           |
'--------------+-------------------+----------------------------------------'
| (unbound)    | overwrite-mode    | Toggle overwrite mode.                 |
'--------------'-------------------'----------------------------------------'
| Killing and Yanking:                                                      |
'--------------.-------------------.----------------------------------------'
| C-k          | kill-line         | Kill the text from point to the end of |
|              |                   | the line.                              |
'--------------+-------------------+----------------------------------------'
| C-u          | unix-line-discard | Kill backward from point to the        |
|              |                   | beginning of the line.                 |
'--------------+-------------------+----------------------------------------'

另外,这个readline快捷键在很多命令输入的地方都适用,比如MySQL客户端等。

Bash配置文件

可以参考bash(1)手册页中的FILES里的说明,这里罗列一下:

FILES
       /bin/bash
              The bash executable
       /etc/profile
              The systemwide initialization file, executed for login shells
       /etc/bash.bash_logout
              The systemwide login shell cleanup file, executed when a login shell exits
       ~/.bash_profile
              The personal initialization file, executed for login shells
       ~/.bashrc
              The individual per-interactive-shell startup file
       ~/.bash_logout
              The individual login shell cleanup file, executed when a login shell exits
       ~/.inputrc
              Individual readline initialization file

/etc目录下的配置文件是系统级的配置文件,有首先加载。用户主目录下的文件则会在用户登录的时候加载。
这里需要注意的是profilebash作为登录shell的时候才会执行,而.bashrc是交互shell时执行命令。rc正是run command的缩写。所以,这里需要注意的一点是,用户环境变量的设置一般要放在~/.bash_profile, 否则如果放在~/.bashrc中时,如果以非交互shell的方式运行bash,则会导致用户的环境变量得不到设置。另外,登录shell也就是需要做登录时启动的shell。再另外su - appusersu appuser的主要区别也是前者会模拟登录shell,加载~/.bashr_profile

命令组合

可以使用||以及&&来实现简化操作,例如:

[ -d /tmp/foo ] && echo '/tmp/foo exist' || mkdir /tmp/foo

循环

# ls -l *.h
for f in *.h
do
ls -l $f
done

# md5sum execute file of process which is end with `-s 200'
for pid in $(pgrep -f -- '-s 200$')
do
md5sum  $(readlink /proc/${pid}/exe)
done | sort

可以使用内置命令for执行一般的循环操作。

文本去重

# 去重并保持输入顺序
awk '!a[$0]++'
awk '!a[$0]++ {print}'
awk '!a[$0]++ {print $0}'

# 去重并排序
awk '!a[$0]++' | sort

这里前3条命令的效果是一样的。awk的默认行为是打印$0,也就是打印整行。

使用trap在脚本内捕捉信号

readonly PROGRAM=$(basename $0)
readonly TMP_OUTPUT=/tmp/${PROGRAM}.tmp

trap clean EXIT

function clean()
{
    rm -f $TMP_OUTPUT
}

上面的例子在脚本退出的时候,调用自定义函数clean执行清理工作,不管脚本是正常退出还是被kill。

原子操作

有时需要在脚本里执行原子操作,可以使用mkdir的检测目录是否存在和创建目录为原子操作实现。(注意,在NTFS文件系统中为非原子操作)。

readonly PROGRAM=$(basename $0)
readonly LOCK_DIR=/tmp/${PROGRAM}.lock
trap unlock EXIT

function unlock ()
{
    rmdir -f ${LOCK_DIR}
}

if mkdir ${LOCK_DIR}; then
    # get lock, do what we want here...
    ...
    unlock
fi

了解并使用Linux系统的特殊文件

/dev/stdin
: 标准输入。一般也可以- 表示标准输入。例如:

# grep from stdin
grep 'abc'

# also grep from stdin
grep 'abc' -

# still grep from stdin
grep 'abc' /dev/stdin

/dev/stdout
: 标准输出

/dev/stderr
: 标准错误

/dev/null
: 空文件,读入马上返回EOF;写入内容则被丢弃。

/dev/zero
: zero文件,读入返回0;写入内容会被丢去。

/dev/random
: 随机数发生器。

/dev/urandom
: 非阻塞的随机数发生器。

/proc/pid/
: 进程信息伪文件系统。可以在里查找到进程的相关信息。具体参考手册man 5 proc以及维基百科procfs

把进程放后台的4种方法

# run at background
sleep 30 &

# run at background and remove jobs from current shell
sleep 30 & disown

# ignore hang signal
nohup sleep 30

# execute command in sub-shell at backgroud and also make sub-shell run at backgroud
(sleep 10 &)&

一个好用的目录跳转工具

如果仅使用cd在目录之间跳转,往往需要输入很多字符,效率很低。这里推荐一个简单的shell脚本z
这里只是举例说明一下,有很多好用的辅助工具可以改进命令行操作的体验。

参考

命令行的艺术
Unix & Linux大学教程
Shell脚本学习指南
UNIX用户手册
UNIX环境高级编程

上一篇 下一篇

猜你喜欢

热点阅读