高级Bash脚本编程指南(28):文件与归档命令(一)
成于坚持,败于止步
归档命令
tar
标准的UNIX归档工具. 起初这只是一个磁带归档程序, 而现在这个工具已经被开发为通用打包程序, 它能够处理所有设备的所有类型的归档文件, 包括磁带设备, 正常文件, 甚至是stdout. GNU的tar工具现在可以接受不同种类的压缩过滤器
比如tar czvf archive_name.tar.gz *, 并且可以递归的处理归档文件, 还可以用gzips压缩目录下的所有文件, 除了当前目录下($PWD)的点文件.
一些有用的tar命令选项:
-c 创建(一个新的归档文件)
-x 解压文件(从存在的归档文件中)
--delete 删除文件(从存在的归档文件中)
这个选项不能用于磁带类型设备.
-r 将文件添加到现存的归档文件的尾部
-A 将tar文件添加到现存的归档文件的尾部
-t 列出现存的归档文件中包含的内容
-u 更新归档文件
-d 使用指定的文件系统, 比较归档文件
-z 用gzip压缩归档文件(压缩还是解压, 依赖于是否组合了-c或-x)选项
-j 用bzip2压缩归档文件
如果想从损坏的用gzipped压缩过的tar文件中取得数据, 那将是非常困难的. 所以当我们归档重要的文件的时候, 一定要保留多个备份.
分享个我一直使用的解压,压缩命令集:http://blog.csdn.net/xinyuwuxian/article/details/8872730 O(∩_∩)O~
shar
Shell归档工具. 存在于shell归档文件中的所有文件都是未经压缩的, 并且本质上是一个shell脚本, 以#!/bin/sh开头, 并且包含所有必要的解档命令. Shar 归档文件至今还在Internet新闻组中使用, 否则的话, shar早就被tar/gzip所取代了. unshar命令用来解档shar归档文件.
ar
创建和操作归档文件的工具, 主要在对二进制目标文件打包成库时才会用到.
rpm
Red Hat包管理器, 或者说rpm工具提供了一种对源文件或二进制文件进行打包的方法. 除此之外, 它还包括安装命令, 并且还检查包的完整性.
一个简单的rpm -i package_name.rpm命令对于安装一个包来说就足够了, 虽然这个命令还有好多其它的选项.
rpm -qf 列出一个文件属于那个包
bash$ rpm -qf /bin/ls
coreutils-5.2.1-31
rpm -qa将会列出给定系统上所有安装了的 rpm包. rpm -qa package_name命令将会列出与给定名字package_name相匹配的包
bash$ rpm -qa
redhat-logos-1.1.3-1
glibc-2.2.4-13
cracklib-2.7-12
dosfstools-2.7-1
gdbm-1.8.0-10
ksymoops-2.4.1-1
mktemp-1.5-11
perl-5.6.0-17
reiserfs-utils-3.x.0j-2
...
bash$ rpm -qa docbook-utils
docbook-utils-0.6.9-2
bash$ rpm -qa docbook | grep docbook
docbook-dtd31-sgml-1.0-10
docbook-style-dsssl-1.64-3
docbook-dtd30-sgml-1.0-10
docbook-dtd40-sgml-1.0-11
docbook-utils-pdf-0.6.9-2
docbook-dtd41-sgml-1.0-10
docbook-utils-0.6.9-2
一直在用ubuntu,red hat几乎不在考虑范围之内,O(∩_∩)O~,这里知道就好
cpio
这个特殊的归档拷贝命令(拷贝输入和输出, copy input and output)现在已经很少能见到了, 因为它已经被tar/gzip所替代了. 现在这个命令只在一些比较特殊的地方还在使用, 比如拷贝一个目录树. 如果指定一个合适尺寸的块(用于拷贝), 那么这个命令会比tar命令快一些.
#!/bin/bash
# 使用cpio来拷贝目录树.
# 使用'cpio'的优点:
# 加速拷贝. 比通过管道使用'tar'命令快一些.
# 很适合拷贝一些'cp'命令
#+ 搞不定的的特殊文件(比如名字叫pipes的文件, 等等)
ARGS=2
E_BADARGS=65
if [ $# -ne "$ARGS" ]
then
echo "Usage: `basename $0` source destination"
exit $E_BADARGS
fi
source=$1
destination=$2
find "$source" -depth | cpio -admvp "$destination"
# ^^^^^ ^^^^^
# 阅读'find'和'cpio'的man页来了解这些选项的意义.
# 练习:
# -----
# 添加一些代码来检查'find | cpio'管道命令的退出码($?)
#+ 并且如果出现错误的时候输出合适的错误码.
exit 0
结果:
root@ubuntu:~/resource/shell-study/0622-2013# ./test1.sh /root/resource/shell-study/0621-2013/ /root/resource/shell-study/
/root/resource/shell-study//root/resource/shell-study/0621-2013/test1.sh
/root/resource/shell-study//root/resource/shell-study/0621-2013/test10.sh
/root/resource/shell-study//root/resource/shell-study/0621-2013/file.unx
/root/resource/shell-study//root/resource/shell-study/0621-2013/file-d
/root/resource/shell-study//root/resource/shell-study/0621-2013/test7.sh
/root/resource/shell-study//root/resource/shell-study/0621-2013/test6.sh
/root/resource/shell-study//root/resource/shell-study/0621-2013/test8.sh
/root/resource/shell-study//root/resource/shell-study/0621-2013/test2.sh
/root/resource/shell-study//root/resource/shell-study/0621-2013/file-u
/root/resource/shell-study//root/resource/shell-study/0621-2013/test4.sh
/root/resource/shell-study//root/resource/shell-study/0621-2013/test9.sh
/root/resource/shell-study//root/resource/shell-study/0621-2013/test3.sh
/root/resource/shell-study//root/resource/shell-study/0621-2013/file
/root/resource/shell-study//root/resource/shell-study/0621-2013/test5.sh
/root/resource/shell-study//root/resource/shell-study/0621-2013/
14 blocks
root@ubuntu:~/resource/shell-study/0622-2013# ls
test1.sh
root@ubuntu:~/resource/shell-study/0622-2013# cd ..
root@ubuntu:~/resource/shell-study# ls
0426-2013 0504-2013 0507-2013 0509-2013 0613-2013 0615-2013 0619-2013 0621-2013 root
0427-2013 0506-2013 0508-2013 0609-2013 0614-2013 0618-2013 0620-2013 0622-2013
root@ubuntu:~/resource/shell-study# cd root/resource/shell-study/0621-2013/
file file-d file-u file.unx test10.sh test1.sh test2.sh test3.sh test4.sh test5.sh test6.sh test7.sh test8.sh test9.sh
root@ubuntu:~/resource/shell-study# cd root/resource/shell-study/0621-2013/
rpm2cpio
这个命令可以从rpm归档文件中解出一个cpio归档文件.
一个实例:解包一个rpm归档文件
#!/bin/bash
# de-rpm.sh: 解包一个'rpm'归档文件
: ${1?"Usage: `basename $0` target-file"}
# 必须指定'rpm'归档文件名作为参数.
TEMPFILE=$$.cpio # Tempfile必须是一个"唯一"的名字.
# $$是这个脚本的进程ID.
rpm2cpio < $1 > $TEMPFILE # 将rpm归档文件转换为cpio归档文件.
cpio --make-directories -F $TEMPFILE -i # 解包cpio归档文件.
rm -f $TEMPFILE # 删除cpio归档文件.
exit 0
压缩命令
gzip
标准的GNU/UNIX压缩工具, 取代了比较差的compress命令. 相应的解压命令是gunzip, 与gzip -d是等价的.
-c选项将会把gzip的输出打印到stdout上. 当你想通过管道传递到其他命令的时候, 这就非常有用了.
zcat过滤器可以将一个gzip文件解压到stdout, 所以尽可能的使用管道和重定向. 这个命令事实上就是一个可以工作于压缩文件(包括一些的使用老的compress工具压缩的文件)的cat命令. zcat命令等价于gzip -dc.
在某些商业的UNIX系统上, zcat与uncompress -c等价, 并且不能工作于gzip文件.
bzip2
用来压缩的一个可选的工具, 通常比gzip命令压缩率更高(所以更慢), 适用于比较大的文件. 相应的解压命令是bunzip2.
compress, uncompress
这是一个老的, 私有的压缩工具, 一般的商业UNIX发行版都会有这个工具. 更有效率的gzip工具早就把这个工具替换掉了. Linux发行版一般也会包含一个兼容的compress命令, 虽然gunzip也可以解压用compress工具压缩的文件.
znew命令可以将compress压缩的文件转换为gzip压缩的文件.
sq
另一种压缩工具, 一个只能工作于排过序的ASCII单词列表的过滤器. 这个命令使用过滤器标准的调用语法, sq < input-file > output-file. 速度很快, 但是效率远不及gzip. 相应的解压命令为unsq, 调用方法与sq相同.
sq的输出可以通过管道传递给gzip, 以便于进一步的压缩.
zip, unzip
跨平台的文件归档和压缩工具, 与DOS下的pkzip.exe兼容. "Zip"归档文件看起来在互联网上比"tar包"更流行.
unarc, unarj, unrar
这些Linux工具可以用来解档那些用DOS下的arc.exe, arj.exe, 和rar.exe 程序进行归档的文件.
文件信息
file
确定文件类型的工具. 命令file file-name将会用ascii文本或数据的形式返回file-name文件的详细描述. 这个命令会使用/usr/share/magic, /etc/magic, 或/usr/lib/magic中定义的魔法数字来标识包含某种魔法数字的文件, 上边所举出的这3个文件需要依赖于具体的 Linux/UNIX 发行版.
-f选项将会让file命令运行于批处理模式, 也就是说它会分析-f后边所指定的文件, 从中读取需要处理的文件列表, 然后依次执行file命令.
-z选项, 当对压缩过的目标文件使用时, 将会强制分析压缩的文件类型
bash$ file test.tar.gz
test.tar.gz: gzip compressed data, deflated, last modified: Sun Sep 16 13:34:51 2001, os: Unix
bash file -z test.tar.gz
test.tar.gz: GNU tar archive (gzip compressed data, deflated, last modified: Sun Sep 16 13:34:51 2001, os: Unix)
#!/bin/bash
# 在给定的目录中找出sh和Bash脚本文件:
DIRECTORY=/usr/bin
KEYWORD=Bourne
# Bourne和Bourne-Again shell脚本
file $DIRECTORY/* | fgrep $KEYWORD
exit 0
结果:
root@ubuntu:~/resource/shell-study/0622-2013# ./test2.sh
/usr/bin/aptitude-create-state-bundle: Bourne-Again shell script text executable
/usr/bin/aptitude-run-state-bundle: Bourne-Again shell script text executable
/usr/bin/cautious-launcher: Bourne-Again shell script text executable
/usr/bin/checkbox-gtk: Bourne-Again shell script text executable
/usr/bin/f-spot: Bourne-Again shell script text executable
/usr/bin/f-spot-import: Bourne-Again shell script text executable
/usr/bin/lcf: Bourne-Again shell script text executable
/usr/bin/ldd: Bourne-Again shell script text executable
/usr/bin/net-snmp-config: Bourne-Again shell script text executable
/usr/bin/rarian-sk-config: Bourne-Again shell script text executable
/usr/bin/rarian-sk-extract: Bourne-Again shell script text executable
/usr/bin/rarian-sk-rebuild: Bourne-Again shell script text executable
/usr/bin/rarian-sk-update: Bourne-Again shell script text executable
/usr/bin/startx: Bourne-Again shell script text executable
/usr/bin/tzselect: Bourne-Again shell script text executable
/usr/bin/ucf: Bourne-Again shell script text executable
/usr/bin/ucfr: Bourne-Again shell script text executable
/usr/bin/usb-devices: Bourne-Again shell script text executable
/usr/bin/vm-support: Bourne-Again shell script text executable
root@ubuntu:~/resource/shell-study/0622-2013#
一个稍难的实例,这里如果是初学者,建议你先别研究这一部分
#!/bin/bash
# strip-comment.sh: 去掉C程序中的注释(/* 注释 */).
E_NOARGS=0
E_ARGERROR=66
E_WRONG_FILE_TYPE=67
if [ $# -eq "$E_NOARGS" ]
then
echo "Usage: `basename $0` C-program-file" >&2 # 将错误消息发到stderr.
exit $E_ARGERROR
fi
# 检查文件类型是否正确.
type=`file $1 | awk '{ print $2, $3, $4, $5 }'`
# "file $1" echo出文件类型 . . .
# 然后awk会删掉第一个域, 就是文件名 . . .
# 然后结果将会传递到变量"type"中.
correct_type="ASCII C program text"
if [ "$type" != "$correct_type" ]
then
echo
echo "This script works on C program files only."
echo
exit $E_WRONG_FILE_TYPE
fi
# 相当隐秘的sed脚本:
#--------
sed '
/^\/\*/d
/.*\*\//d
' $1
#--------
# 如果你花上几个小时来学习sed语法的话, 上边这个命令还是很好理解的.
# 如果注释和代码在同一行上, 上边的脚本就不行了.
#+ 所以需要添加一些代码来处理这种情况.
# 这是一个很重要的练习.
# 当然, 上边的代码也会删除带有"*/"的非注释行 --
#+ 这也不是一个令人满意的结果.
exit 0
# ----------------------------------------------------------------
# 下边的代码不会执行, 因为上边已经'exit 0'了.
# Stephane Chazelas建议使用下边的方法:
usage() {
echo "Usage: `basename $0` C-program-file" >&2
exit 1
}
WEIRD=`echo -n -e '\377'` # 或者WEIRD=$'\377'
[[ $# -eq 1 ]] || usage
case `file "$1"` in
*"C program text"*) sed -e "s%/\*%${WEIRD}%g;s%\*/%${WEIRD}%g" "$1" \
| tr '\377\n' '\n\377' \
| sed -ne 'p;n' \
| tr -d '\n' | tr '\377' '\n';;
*) usage;;
esac
# 如果是下列的这些情况, 还是很糟糕:
# printf("/*");
# 或者
# /* /* buggy embedded comment */
#
# 为了处理上边所有这些特殊情况(字符串中的注释, 含有 \", \\" ...
#+ 的字符串中的注释)唯一的方法还是写一个C分析器
#+ (或许可以使用lex或者yacc?).
exit 0
which
which command-xxx将会给出"command-xxx"的完整路径. 当你想在系统中准确定位一个特定的命令或工具的时候, 这个命令就非常有用了.
root@ubuntu:~/resource/shell-study/0622-2013# which which
/usr/bin/which
root@ubuntu:~/resource/shell-study/0622-2013#
whereis
与上边的which很相似, whereis command-xxx不只会给出"command-xxx"的完整路径, 而且还会给出这个命令的man页的完整路径.
root@ubuntu:~/resource/shell-study/0622-2013# whereis which
which: /bin/which /usr/bin/which /usr/share/man/man1/which.1.gz
root@ubuntu:~/resource/shell-study/0622-2013#
whatis
whatis filexxx将会在whatis数据库中查询"filexxx". 当你想确认系统命令和重要的配置文件的时候, 这个命令就非常重要了. 可以把这个命令认为是一个简单的man命令.
root@ubuntu:~/resource/shell-study/0622-2013# whatis which
which (1) - locate a command
root@ubuntu:~/resource/shell-study/0622-2013#
来看一个实例
#!/bin/bash
# 所有在/usr/bin中的神秘2进制文件都是些什么东西?
DIRECTORY="/usr/bin"
for file in $DIRECTORY/*
do
whatis `basename $file` # 将会echo出这个2进制文件的信息.
done
exit 0
# 你可能希望将这个脚本的输出重定向, 像这样:
# ./sh >>whatis.db
# 或者一页一页的在stdout上察看,
# ./sh | less
结果:
root@ubuntu:~/resource/shell-study/0622-2013# ./test3.sh
[ (1) - check file types and compare values
2to3: nothing appropriate.
2to3-2.6: nothing appropriate.
a2p (1) - Awk to Perl translator
abrowser: nothing appropriate.
aconnect (1) - ALSA sequencer connection manager
acpi_fakekey (1) - acpi event wrapper
acpi_listen (8) - ACPI event listener
add-apt-repository (1) - Adds a repository into the /etc/apt/sources.list or /etc/apt/sources.list.d
addpart (8) - simple wrapper around the "add partition" ioctl
addr2line (1) - convert addresses into file names and line numbers.
alacarte (1) - edit freedesktop.org menus
alsamixer (1) - soundcard mixer for ALSA soundcard driver, with ncurses interface
amidi (1) - read from and write to ALSA RawMIDI ports
amixer (1) - command-line mixer for ALSA soundcard driver
......
vdir
显示详细的目录列表. 与ls -l的效果相似.
root@ubuntu:~/resource/shell-study/0622-2013# ls
test1.sh test2.sh test3.sh
root@ubuntu:~/resource/shell-study/0622-2013# vdir
total 12
-rwxr-xr-x 1 root root 700 2013-06-21 17:39 test1.sh
-rwxr-xr-x 1 root root 179 2013-06-21 18:05 test2.sh
-rwxr-xr-x 1 root root 357 2013-06-21 18:21 test3.sh
root@ubuntu:~/resource/shell-study/0622-2013#
locate, slocate
locate命令将会在预先建立好的档案数据库中查询文件. slocate命令是locate的安全版本(locate命令很有可能已经被关联到slocate命令上了).
$bash locate hickson
/usr/lib/xephem/catalogs/hickson.edb
显示符号链接所指向的文件.
root@ubuntu:~/resource/shell-study/0622-2013# readlink /usr/bin/awk
/etc/alternatives/awk
root@ubuntu:~/resource/shell-study/0622-2013#
strings
使用strings命令在二进制或数据文件中找出可打印字符. 它将在目标文件中列出所有找到的可打印字符的序列. 这个命令对于想进行快速查找n个字符的打印检查来说是很方便的, 也可以用来检查一个未知格式的图片文件(strings image-file | more可能会搜索出像JFIF这样的字符串, 那么这就意味着这个文件是一个jpeg格式的图片文件). 在脚本中, 你可能会使用grep或者sed命令来分析strings命令的输出.
#!/bin/bash
#
# 这个脚本将会通过排除标准单词列表的形式
#+ 来过滤"strings"命令的输出.
# 这将有效的过滤掉无意义的字符,
#+ 并且只会输出可以识别的字符.
# ===========================================================
# 脚本参数的标准检查
ARGS=1
E_BADARGS=65
E_NOFILE=66
if [ $# -ne $ARGS ]
then
echo "Usage: `basename $0` filename"
exit $E_BADARGS
fi
if [ ! -f "$1" ] # 检查文件是否存在.
then
echo "File \"$1\" does not exist."
exit $E_NOFILE
fi
# ===========================================================
MINSTRLEN=3 # 最小的字符串长度.
WORDFILE=/usr/share/dict/words # 字典文件.
# 也可以指定一个不同的单词列表文件,
#+ 但这种文件必须是以每个单词一行的方式进行保存.
wlist=`strings "$1" | tr A-Z a-z | tr '[:space:]' Z | \
tr -cs '[:alpha:]' Z | tr -s '\173-\377' Z | tr Z ' '`
# 将'strings'命令的输出通过管道传递到多个'tr'命令中.
# "tr A-Z a-z" 全部转换为小写字符.
# "tr '[:space:]'" 转换空白字符为多个Z.
# "tr -cs '[:alpha:]' Z" 将非字母表字符转换为多个Z,
#+ 然后去除多个连续的Z.
# "tr -s '\173-\377' Z" 把所有z后边的字符都转换为Z.
#+ 并且去除多余重复的Z.
#+ (注意173(123 ascii "{")和377(255 ascii 最后一个字符)都是8进制)
#+ 这样处理之后, 我们所有之前需要处理的令我们头痛的字符
#+ 就全都转换为字符Z了.
# 最后"tr Z ' '" 将把所有的Z都转换为空格,
#+ 这样我们在下边循环中用到的变量wlist中的内容就全部以空格分隔了.
# ****************************************************************
# 注意, 我们使用管道来将多个'tr'的输出传递到下一个'tr'时
#+ 每次都使用了不同的参数.
# ****************************************************************
for word in $wlist # 重要:
# $wlist 这里不能使用双引号.
# "$wlist" 不能正常工作.
# 为什么不行?
do
strlen=${#word} # 字符串长度.
if [ "$strlen" -lt "$MINSTRLEN" ] # 跳过短的字符串.
then
continue
fi
grep -Fw $word "$WORDFILE" # 只匹配整个单词.
# ^^^ # "固定字符串" 和
#+ "整个单词" 选项.
done
exit $?
结果:
root@ubuntu:~/resource/shell-study/0622-2013# cat test1.sh
#!/bin/bash
# 使用cpio来拷贝目录树.
# 使用'cpio'的优点:
# 加速拷贝. 比通过管道使用'tar'命令快一些.
# 很适合拷贝一些'cp'命令
#+ 搞不定的的特殊文件(比如名字叫pipes的文件, 等等)
ARGS=2
E_BADARGS=65
if [ $# -ne "$ARGS" ]
then
echo "Usage: `basename $0` source destination"
exit $E_BADARGS
fi
source=$1
destination=$2
find "$source" -depth | cpio -admvp "$destination"
# ^^^^^ ^^^^^
# 阅读'find'和'cpio'的man页来了解这些选项的意义.
# 练习:
# -----
# 添加一些代码来检查'find | cpio'管道命令的退出码($?)
#+ 并且如果出现错误的时候输出合适的错误码.
exit 0
root@ubuntu:~/resource/shell-study/0622-2013# ./test4.sh test
test1.sh test2.sh test3.sh test4.sh
root@ubuntu:~/resource/shell-study/0622-2013# ./test4.sh test1.sh
bin
bin's
bash
tar
tar's
pipes
then
echo
echo's
usage
usage's
source
source's
destination
destination's
exit
exit's
source
source's
destination
destination's
find
source
source's
depth
depth's
destination
destination's
find
find
exit
exit's
root@ubuntu:~/resource/shell-study/0622-2013#
先到这里了,O(∩_∩)O~
我的专栏地址:http://blog.csdn.net/column/details/shell-daily-study.html
待续。。。。