Bash 常用技巧整理

2019-05-01 language bash

整理一些常见 Bash 的使用技巧,例如 Here Document、特殊字符文件处理、字符集设置等。

Here Document

使用 echo 添加到文件比较麻烦,如果存在 $ 则需要做转义,或者使用 EOF 也可以,如下是示例。

$ cat << EOF >> /tmp/foobar.conf
net.core.rmem_default = 262144
net.core.rmem_max = 262144
net.core.wmem_default = 262144
net.core.wmem_max = 262144
export PATH=\$PATH:\$HOME/bin
EOF

在此使用的就是 Here Document,这是一种在 Linux Shell 中的一种特殊的重定向方式,它的基本的形式如下,通常 delimiter 使用 EOF :

cmd << delimiter
  Here Document Content
delimiter

也可以在终端中输入 cat << EOF ,然后输入多行信息,最终以 EOF 结束,其中间输入的信息将会回显在屏幕上。

另外,可以通过 <<- 删除 Here Document 的内容部分每行前面的 tab (制表符) , 这种用法是为了编写 Here Document 的时候可以将内容部分进行缩进,方便阅读代码。

特殊字符文件处理

在 Linux 中,文件名最大长度可达 256 字符,可用字符有字母、数字、'.'(点)、'_'(下划线)、'-'(连字符)、' '(空格),其中开始字符不建议使用 '_''-'' ' 字符,'/'(反斜线) 用于标示目录,不能用作文件或者文件夹名称。

另外,在 shell 中,'?'(问号)、'*'(星号)、'&' 字符有特殊含义,同样不建议使用。

在 shell 中,将 -- 之后的内容当作文件。

$ cd .>-a                  # 创建一个文件,或者 >-a
$ vi -- -a                 # 编辑,或者 echo "">-a
$ rm -- -a                 # 删除,或者 rm ./-a
$ touch '><!*'             # 创建
$ touch '?* $&'            # 创建

对于这样的文件,可以执行如下操作。

----- 将非乱码的文件移出到某个目录下
$ find . -name "[a-z|A-Z]*" | xargs -I {} mv {} /somepath

----- 也可以通过inode删除
$ ls -i
$ find -inum XXX | xargs -I {} rm {}
$ find -inum XXX -delete

如果文件的文件名含有终端无法正确显示的字符,那么可以通过 inode 来删除,处理命令如下。

----- 查看文件innode
# ls -li
total 0
358315 -rw-r--r-- 1 root root 0 Apr 6 23:13 ???}

----- 通过inode删除文件,如下两种方式相同
# find . -inum 358315 -delete
# rm -i `find . -maxdepth 1 -inum 358315 -print`

字符集设置

程序运行时需要使用一套语言环境,包括了:字符集 (数据) 和字体 (显示),在 Linux 中通过 locale 来设置程序运行的不同语言环境,locale 由 ANSI C 提供支持,可以根据不同的国家地区设置不同的语言环境。

locale 的命名规则为 <语言>_<地区>.<字符集编码> ,例如 zh_CN.UTF-8zh 代表中文,CN 代表大陆地区,UTF-8 表示字符集。另外,在 locale 会通过一组环境变量,针对不同场景配置。

LC_COLLATE
  定义该环境的排序和比较规则
LC_CTYPE
  用于字符分类和字符串处理,控制所有字符的处理方式,包括字符编码,字符是单字节还是
  多字节,如何打印等,是最重要的一个环境变量。
LC_MONETARY
  货币格式。
LC_NUMERIC
  非货币的数字显示格式。
LC_TIME
  时间和日期格式。
LC_MESSAGES
  提示信息的语言,与之详细的还有LANGUAGE参数,当LANGUAGE设置后LC_MESSAGES将会失效,
  而且可同时设置多种语言信息,如LANGUANE="zh_CN.GB18030:zh_CN.GB2312:zh_CN"。
LANG
  LC_*的默认值,是最低级别的设置,如果LC_*没有设置,则使用该值。
LC_ALL
  一个宏,如果该值设置了,则该值会覆盖所有LC_*的设置值。注意,LANG的值不受该宏影响。

简单介绍下常见的操作。

----- 设置成中文环境
export LANG="zh_CN.UTF-8"
export LANGUAGE="zh_CN:zh:en_US:en"

----- 设置成英文环境
export LANG="en_US.UTF-8"
export LANGUAGE="en_US:en"

----- 查看现有语言环境
$ locale

----- 所有可用语言环境
$ locale -a

注意,图形界面可能需要更多的设置,暂时先不讨论了。

获取脚本绝对路径

在使用脚本时经常会有诉求获取当前脚本的路径,如果简单使用 PWD=$(dirname $0) 命令,容易导致使用不同的命令行导致不同的值,脚本如下。

#!/bin/bash
PWD=$(dirname $0)

cd /opt
cp hello.txt ${PWD}

本意是将 /opt/hello.txt 文件复制到脚本所在路径,但是当使用不同的调用方式时,实际得到的 PWD 值是不同的,如下是不同脚本以及 pwd 命令返回的结果。

执行脚本${PWD} 变量值pwd 命令
sh test.sh./tmp/demo
sh ./test.sh./tmp/demo
sh demo/test.shdemo/tmp
sh ./demo/test.sh./demo/tmp
sh /tmp/demo/test.sh/tmp/demo/

正确的方式是 PWD=$(dirname $(readlink -f $0)) 也就是先通过 readlink 命令获取绝对路径,然后再取出目录,这样获取的 $PWD 值始终为 /tmp/demo 目录了。

其它

目录合并

可以将两个目录通过 cp -r dir1/* dir2/* merged/ 进行合并,由于是复制,对于较大的文件会导致速度较慢。如果通过 mv 则会报 Directory not empty 的错误。

可以通过如下命令采用硬链接的方式进行复制,通过 tree --inodes 命令查看文件的 inode 号。

$ cp -r --link dir1/* dir2/* merged/

日期转换

----- 日期转换
$ date +%s -d "04/24/2014 15:30:00"         // 将日期转换成时间戳
$ date -d @1398324600                       // 将时间戳转换成日期
$ date +%s                                  // 将当前日期转换成时间戳