LinuxShell脚本攻略--第二章 命令之乐

时间:2023-03-09 20:06:02
LinuxShell脚本攻略--第二章 命令之乐

用 cat 进行拼接 
文件查找与文件列表
玩转 xargs 
用 tr 进行转换
排序
临时文件命名与随机数
分割文件和数据
根据扩展名切分文件名
mv 批量重命名文件
交互输入自动化

cat:

echo 'Text through stdin' | cat - file.txt    #  - 被作为 stdin 文本的文件名。
$ cat -s file #压缩相邻的空白行
$ cat -T file.py #将制表符显示为^I
$ cat -n lines.txt #显示行号

find:

$ find . -print
$ find .

这两条命令效果是一样的。都是查找当前目录下的所有的文件及目录,使用-print时‘\n’作为用于输出的文件名进行分隔,-print0 指明了使用‘\0’ 进行分隔。

$ find /home/slynux -name "*.txt" -print       #按文件名查找
$ find . -iname "example*" -print #按文件名不区分大小写查找
$ find . \( -name "*.txt" -o -name "*.pdf" \) -print # -o OR条件 ()将括号中的语句作为一个整体
$ find /home/users -path "*/slynux/*" -print # -name 用给定的文件名匹配,-path将文件路径作为一个整体进行匹配。
$ find . -regex ".*\(\.py\|\.sh\)$" #正则表达式 查找.py或.sh文件
$ find . -iregex ".*\(\.py\|\.sh\)$" #不区分大小写
$ find . ! -name "*.txt" -print # !否定。不已.txt结尾的文件
$ find . -maxdepth 1 -name "f*" -print #基于目录的深度查找 -maxdepth -mindepth

在有 -maxdepth 或 -mindepth时,这两个参数应该作为第三个参数出现。如果作为第4个或之后的参数,就可能会影响到 find 的效率,因为它不得不进行一些不必要的检查。例如,如果 -maxdepth 作为第四个参数, -type 作为第三个参数, find首先会找出符合 -type 的所有文件,然后在所有匹配的文件中再找出符合指定深度的那些。但是如果反过来,目录深度作为第三个参数, -type 作为第四个参数,那么 find 就能够在找到所有符合指定深度的文件后,再检查这些文件的类型,这才是最有效的搜索之道。
-type 按类型查找

文件类型       类型参数
普通文件         f
符号链接         l
目录           d
字符设备         c
块设备          b
套接字          s
FIFO             p

$ find . -type d -print    #列出所有的目录

Unix/linux文件系统中每个文件都有三种时间戳:

访问时间( -atime  ):用户最近一次访问文件的时间。
修改时间( -mtime ):文件内容最后一次被修改的时间。
变化时间( -ctime  ):文件元数据(例如权限或所有权)最后一次改变的时间。

-atime 、 -mtime 、 -ctime 可作为 find 的时间选项。它们可以用整数值指定,单位是天。这些整数值通常还带有 - 或 + : - 表示小于, + 表示大于。

$ find . -type f -atime - -print        #打印出在最近7天内被访问过的所有文件
$ find . -type f -atime 7 -print #打印出恰好在7天前被访问过的所有文件
$ find . -type f -atime +7 -print     #打印出访问时间超过7天的所有文件

-amin (访问时间),-mmin (修改时间),-cmin (变化时间)  这些时间的计量单位是“ 分钟 ”。

使用 -newer ,我们可以指定一个用于比较时间戳的参考文件,然后找出比参考文件更新的(更近的修改时间)所有文件

$ find . -type f -newer file.txt -print    #找出比file.txt修改时间更近的所有文件

基于文件大小的搜索:-size

$  find . -type f -size +2k        # 大于2KB的文件
  • b —— 块(512字节)
  • c —— 字节。
  • w —— 字(2字节)
  • k —— 1024字节
  • M —— 1024K字节
  • G —— 1024M字节
$ find . -type f -name "*.swp" -delete    #删除匹配的文件:
$ find . -type f -perm 644 -print       # 打印出权限为644的文件
$ find . -type f -user fzk -print       #打印出用户fzk拥有的文件
# find . -type f -user root -exec chown fzk {} \;
#找到root用户的文件,执行chown fzk {},这里{}会替换成相应的文件。如果找到1.txt,2.txt。
它会被解析为 chown fzk test1.txt 和 chown fzk test2.txt。相当于两条命令
想在一条命令中修改一个列表的文件,在exec中使用+来代替
$ find . -type f -name "*.c" -exec cat {} \;>all_c_files.txt #找到所有c文件,写入到单一文件

跳过指定的目录:

$ find devel/source_path \( -name ".git" -prune \) -o \( -type f -print \)

以上命令打印出不包括在.git目录中的所有文件的名称(路径)。\( -name ".git" -prune \) 的作用是用于进行排除,它指明了 .git目录应该被排除在外,而 \( -type f -print \) 指明了需要执行的动作。这些动作需要被放置在第二个语句块中(打印出所有文件的名称和路径)。

xargs:

xargs 能够处理 stdin 并将其转换为特定命令的命令行参数。 xargs 也可以将单行或多行文本输入转换成其他格式,例如单行变多行或是多行变单行。

$ cat example.txt | xargs          #多行变单行
1 2 3 4 5 6
$ echo | xargs -n #单行变多行
1 2 3
4 5 6
$ echo "splitXsplitXsplitXsplit" | xargs -d X -n 2
split split
split split
$ find . -type f -name "*.txt" -print0 | xargs - rm -f

找到所有.txt删掉。如果用这条命令 find . -type f -name "*.txt" -print | xargs rm -f 文件中的空格会当做文件的分隔符,最后不知道都会删掉什么,-print0 已字符null('\0')来分隔输出,xargs -0 将\0 作为输入定界符。

tr:tr 可以对来自标准输入的内容进行字符替换、字符删除以及重复字符压缩。它可以将一组字符变成另一组字符,因而通常也被称为转换(translate)命令。

$ echo "HELLO WHO IS THIS" | tr 'A-Z' 'a-z'
hello who is this
$ echo "Hello 123 world 456" | tr -d '0-9'   #-d 删除字符
Hello world
$ echo hello 1 char 2 next 4 | tr -d -c '0-9 \n' #-c 补集,删掉除了0-9 空格 换行符的其他字符,也就是只留下这些字符
1 2 4
$ echo "GNU   is       not UNIX. Recursive     right ?  | tr -s ' ' #删掉重复的‘ ’
GNU is not UNIX. Recursive right ?

排序:sort

$ sort file1.txt file2.txt > sorted.txt
$ sort -n file.txt #按照数字进行排序
$ sort -r file.txt #按照逆序进行排序
$ sort -M months.txt #按照月份进行排序(依照一月,二月,三月......)
$ sort -m sorted1 sorted2 #合并两个已排序过的文件
$ sort file1.txt file2.txt | uniq #找出已排序文件中不重复的行

临时文件:

$ filename=`mktemp`   #创建了一个临时文件,并打印出存储在 $filename 中的文件名。
$ echo $filename
/tmp/tmp.8xvhkjF5fH
$ dirname=`mktemp -d` #创建了一个临时目录,并打印出存储在 $dirname 中的目录名。
$ echo $dirname
tmp.NI8xzW7VRX
$ tmpfile=`mktemp -u` #文件名被存储在 $tmpfile 中,但并没有创建对应的文件。
$ echo $tmpfile
/tmp/tmp.RsGmilRpcT
$mktemp test.XXX #根据模板创建临时文件名
test.2tc

分隔文件和数据:

$ split -b 10k data.file -d -a 4 x
$ ls
data.file x0009 x0019 x0029 x0039 x0049 x0059 x0069 x0079

上面的代码将data.file分隔成每个文件大小是10kb的若干个文件。-d是以数字为后缀(没有这个参数是字母),10k还可以用M、G、c(byte)、w(word)、l(line 行数)等。-a length指定后缀的长度。最后一个参数PREFIX,前缀,是什么字符前缀就是什么。

假如分隔一个日志文件server.log,将日志分隔成server1.log, server2.log, server3.log

$ cat server.log

SERVER-1
[connection] 192.168.0.1
[connection] 192.168.0.2
[disconnect] 192.168.0.3
[connection] 192.168.0.4
SERVER-2
[connection] 192.168.0.1
[connection] 192.168.0.2
[disconnect] 192.168.0.3
[connection] 192.168.0.4
SERVER-3
[connection] 192.168.0.1
[connection] 192.168.0.2
[disconnect] 192.168.0.3
[connection] 192.168.0.4

$ csplit server.log /SERVER/ -n  -s {*} -f server -b "%02d.log" ; rm server00.log
$ ls
server01.log server02.log server03.log server.log

csplit 是 split 工具的一个变体。 split 只能够根据数据大小或行数分割文件,而 csplit可以根据文本自身的特点进行分割。

  • /SERVER/ 用来匹配某一行。/[REGEX]/ 表示文本样式。包括从当前行(第一行)直到(但不包括)包含“ SERVER ”的匹配行。
  • {*} 表示根据匹配重复执行分割,直到文件末尾为止。可以用{整数}的形式来指定分割执行的次数。

  • -s 使命令进入静默模式,不打印其他信息。
  • -n 指定分割后的文件名后缀的数字个数,例如01、02、03等
  • -f 指定分割后的文件名前缀(在上面的例子中,server就是前缀)。

  • -b 指定后缀格式。例如 %02d.log ,类似于C语言中 printf 的参数格式。在这里文件名=前缀+后缀= server + %02d.log 。

  • 因为分割后的第一个文件没有任何内容(匹配的单词就位于文件的第一行中),所以我们删除了server00.log。

分隔字符串:

$ file_jpg="sample.jpg"
$ name=${file_jpg%.*}
$ echo File name is: $name
File name is: sample

${VAR%.*} 的含义如下所述:
从 $VAR 中删除位于 % 右侧的通配符(在前例中是. * )所匹配的字符串。通配符从右向左进行匹配。
给 VAR 赋值, VAR=sample.jpg 。那么,通配符从右向左就会匹配到.jpg,因此,从 $VAR中删除匹配结果,就会得到输出 sample

% 属于非贪婪(non-greedy)操作。它从右到左找出匹配通配符的最短结果。还有另一个操作符 %% ,这个操作符与 % 相似,但行为模式却是贪婪的,这意味着它会匹配符合条件的最长的字符串。

$VAR=hack.fun.book.txt
$ echo ${VAR%.*}
hack.fun.book
$ echo ${VAR%%.*}
hack

还有一个:

$ file_jpg="sample.jpg"
$ extension=${file_jpg#*.}
$ echo Extension is: $extension
Extension is: jpg

${VAR#*.} 的含义如下所述:
从 $VAR 中删除位于 # 右侧的通配符(即在前例中使用的 *. )所匹配的字符串。通配符从左向右进行匹配。## 从左向右进行贪婪匹配,并从指定变量中删除匹配结果。
可能发现了,都是删除匹配的为什么还要有两种匹配方式,用${file_jpg%*.}不能代替${file_jpg#*.}吗?看下面

http="www.sample.com"
echo ${http%.com}
echo ${http%.co}
echo ${http%.*} echo ${http#www.}
echo ${http#ww.}
echo ${http#*.}
结果:
www.sample
www.sample.com
www.sample
sample.com
www.sample.com
sample.com

可以看出%只能从最右侧开始匹配,#只能从最左侧开始匹配。

批量重命名移动的例子:运用find和上面的分隔字符串的方法

count=;
for img in `find . -iname '*.png' -o -iname '*.jpg' -type f -maxdepth `
do
new=image-$count.${img##*.}
echo "Renaming $img to $new"
mv "$img" "$new"
let count++
done

实现自动交互输入。

首先有一个运行起来需要你输入参数的脚本interactive.sh

#!/bin/bash
read -p "Enter param1 :" param1;
read -p "Enter param2 :" param2;
echo "you entered : $param1 $param2"

运行前手动输入$ echo -e "1\nhello\n" | ./interactive.sh 和 从文本读取 ./interactive.sh < input.data 都可以实现。
还有一种利用expect来实现。一般的系统中不带expect包,需要自己下载,我的系统是ubuntu14.04 ,在下载expect前需要下载tcl。ubuntu系统直接运行:

sudo apt-get install tcl tk expect

装好了后,建一个expect.sh脚本

#!/usr/bin/expect
spawn ./interactice.sh
expect "Enter param1 :"
send "1\n"
expect "Enter param2 :"
send "2\n"
expect eof
输出:
spawn ./interactive.sh
Enter param1 :1
Enter param2 :2
you entered : 1 2

spwan:指定那个命令需要自动化。expect:提供需要等待的消息。send:等待的消息到达后向终端发送消息。expect eof:命令交互结束。