shell笔记

目录

第1章 小试牛刀

1.1 简介

1.1.1 shell 终端

其形式通常如下:

username@hostname $
或者
root@hostname #
# $表示普通用户,#表示超级用户

当打开一个终端时,最初会执行一组命令来定义诸如提示文本、颜色等各类设置。

这组命令来自位于用户 home 目录中的 .bashrc 脚本文件( ~/.bashrc )。

Bash还维护了一个历史记录文件 ~/.bash_history ,用于保存用户运行过的命令。

~ 是一种简写,代表用户 home 目录的路径。

Bash 中,每个命令或是命令序列是通过使用分号或换行符来分隔 :

$ cmd1 ; cmd2

等同于:

$ cmd1
$ cmd2

1.1.2 运行脚本

shell 脚本通常是一个以 #! 起始的文本文件

#!/bin/bash

**Linux ** 环境下的任何脚本语言,都是以 shebang\#!) 的特殊行作为起始,其置于解释器路径之前。/bin/bashBash 的路径。

一种是将脚本作为 sh 的命令行参数:

$ sh script.sh  # 假设脚本位于当前目录下

另一种是将脚本作为具有执行权限的可执行文件:

$ ./script.sh # ./ 表示当前目录

shell 程序读取脚本的首行,识别是否为 #!/bin/bash ,并在内部以如下命令行执行该脚本:

$ /bin/bash script.sh

另:为使 shell 脚本可独立运行,需具备可执行权限:

$ chmod a+x script.sh

注释部分以 # 起始,一直延续到行尾。shell 不执行脚本代码中的任何注释部分。

1.2 终端打印

在终端中打印文本是 shell 日常需要进行的基本任务。

1.2.1 echo

echo

$ echo Welcome to Bash
Welcome to Bash

echo " "

$ echo "Welcome to Bash"
Welcome to Bash

echo ' '

$ echo 'Welcome to Bash'
Welcome to Bash

echo 打印文本中带特殊字符:

$ echo Hello world !

或者

$ echo 'Hello world !'

或者

$ echo "Hello world \!"    # Escape character \ prefixed.

区别:

  1. 使用不带引号的 echo 时,无法在所要显示的文本中使用分号( ; ),因为分号( ; )在 bash shell 中被用作命令定界符。

  2. 使用带双引号的 echo 时,无法直接打印一些特殊字符,加上一个特殊的转义字符( \ )将其转义。

  3. 使用带单引号的 echo 时,Bash 不会对单引号中的变量(如 $var )求值,而只是照原样显示。

1.2.2 printf

printf 使用的参数和 C语言 中的 printf() 函数类似,通过占位符输出变量。

在默认情况下,printf 并不像 echo 命令一样会自动添加换行符。需要的时候手动添加。

例:

#!/bin/bash
# 文件名: printf.sh

printf  "%-5s %-10s %-4s\n" No Name Mark
printf  "%-5s %-10s %-4.2f\n" 1 Sarath 80.3456
printf  "%-5s %-10s %-4.2f\n" 2 James 90.9989
printf  "%-5s %-10s %-4.2f\n" 3 Jeff 77.564

得到如下格式化的输出:

No   Name      Mark
1    Sarath    80.35
2    James     91.00
3    Jeff      77.56
  • 格式替代符 ( format substitution character )

    %s %c %d %f 都是格式替代符,%s 输出一个字符串,%d 整型输出,%c 输出一个字符,%f 输出实数,以小数形式输出。其所对应的参数可以置于带引号的格式字符串之后。

    %-10s 指一个宽度为 10 个字符(- 表示左对齐,没有则表示右对齐),任何字符都会被显示在 10 个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。

    %-4.2f 指格式化为小数,其中 .2 指保留2位小数。

  • printf 的转义序列

    序列 说明
    \a 警告字符,通常为ASCII的BEL字符
    \b 后退
    \c 抑制(不显示)输出结果中任何结尾的换行字符(只在%b格式指示符控制下的参数字符串中有效),而且,任何留在参数里的字符、任何接下来的参数以及任何留在格式字符串中的字符,都被忽略
    \f 换页(formfeed)
    \n 换行
    \r 回车(Carriage return)
    \t 水平制表符
    \v 垂直制表符
    \ 一个字面上的反斜杠字符
    \ddd 表示1到3位数八进制值的字符。仅在格式字符串中有效
    \0ddd 表示1到3位的八进制值字符

1.2.3 补充内容

echo -n 忽略换行符:

$ echo -n "Welcome to Bash"

echo -e 使用转义序列:

echo -e "1\t2\t3" 
1 2 3

注意

echo和printf中的标志(如-e、-n等)应该出现在命令行内任何字符串之前,否则Bash会将其视为另外一个字符串。

​ 打印彩色输出

​ 每种颜色都有对应的颜色码:

​ 重置=0,黑色=30,红色=31,绿色=32,黄色=33,蓝色=34,洋红=35,青色=36,白色=37。

​ 要打印彩色文本,可输入如下命令:

echo -e "\e[1;31m This is red text \e[0m"

\e[1;31m 将颜色设为红色,\e[0m 将颜色重新置回。

效果如下:

image

如要设置背景颜色,经常使用的颜色码是:重置=0,黑色=40,红色=41,绿色=42,黄色=43,蓝色=44,洋红=45,青色=46,白色=47。

echo -e "\e[1;42m Green Background \e[0m"

效果如下:

image

1.3 变量

shell 中变量用于存放各类数据。

使用时直接赋值即可,不必在使用变量之前声明其类型。

变量的值都以字符串的形式存储。

有一些特殊的变量会被shell环境和操作系统环境用来存储一些特别的值,这类变量称为环境变量。

1.3.1 变量命名规则

shell 中给变量命名时有以下几个规则:

  1. shell 变量的命名只能使用英文字母,数字和下划线,并且开头不能以数字开始。
  2. shell 变量的命名中不能使用标点符号
  3. 不能使用 bash 里的关键字(可用 help 命令查看保留关键字)。

正确的变量名:Namename_name

错误的变量名:3Name(首字母不能时数字)、na?me(不能带除了下划线外的其他标点符号)。

注意

  1. 变量名区分大小写,环境变量建议使用大写便于区分。
  2. 变量和值之间不能有空格,即等号左右两侧不能有空格,如果变量需要有空格则需要使用引号,单引号和双引号在 shell 中有不同的作用。
  3. 单引号不可识别特殊语法,双引号可以识别特殊语法。

给一个变量赋值:

var=value

打印变量的值:

var="value"  # 给变量var赋值
echo $var

或者

echo ${var}

输出如下:

value

可以看出 {} 对变量输出结果无影响,但是一般最好带上 {},这样能很明确的看到变量名。

单引号不识别特殊语法 , 原样输出:

$ var=value
$ var1='$var'
$ echo ${var1}
# 输出
$ $var1

双引号和无引号可识别特殊语法:

$ var2="$var"
$ echo ${var2}
# 输出
$ value

我们可以在 printfecho 命令的双引号中引用变量值。

#!/bin/bash
# 文件名: variables.sh
fruit=apple
count=5
echo "We have $count ${fruit}(s)"

输出如下:

We have 5 apple(s)

1.3.2 环境变量

环境变量是未在当前进程中定义,而从父进程中继承而来的变量。

export 命令用来设置环境变量。

env 命令查看当前所有环境变量的值。

例:环境变量 HTTP_PROXY ,它定义了一个 Internet 连接应该使用哪一个代理服务器。

export HTTP_PROXY="http://192.168.0.2:3128"

export 设置环境变量重启之后失效,若要其永久生效,需将命令写入 /etc/profile中。

vim /etc/profile
....
# 在最后行加入一行
export HTTP_PROXY="http://192.168.0.2:3128"

写入之后使用 soure 命令使其立即生效;

source /etc/profile

至此之后,从当前 shell 脚本执行的任何程序都会继承这个变量。

对于每个进程,在其运行时的环境变量可以使用下面的命令来查看:

cat /proc/$PID/environ

其中,$PID 设置成相关进程的进程 ID。默认输出的每个变量之间由 null 字符( \0 )分割,不便查看,可替换成格式化输出。

格式化输出:

cat /proc/$PID/environ  | tr '\0' '\n'

在默认情况下,有很多标准环境变量可供 shell 使用。

PATH 就是其中之一。通常,变量 PATH 包含:

[root@localhost ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin

在给出所要执行的命令后,shell 自动在 PATH 环境变量所包含的目录列表中(各目录路径之间以冒号分隔)查找对应的可执行文件。

如果需要在 PATH 中添加一条新路径,可在 /etc/profile 中进行修改。

vim /etc/profile
....
# 在最后行加入一行
export PATH="$PATH:/home/user/bin"

使用 soure 生效,查看当前的 PATH 值:

[root@localhost ~]# source /etc/profile
[root@localhost ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/home/user/bin

这样,我们就将 /home/user/bin 添加到了 PATH 中。

还有一些众所周知的环境变量:HOMEPWDUSERUIDSHELL 等。

1.3.3 补充内容

(1)获取变量字符串长度

可用下面的方法获得变量值的长度:

 length=${#var}

例:

[root@localhost ~]# var=12345678901234567890
[root@localhost ~]# echo ${#var}
20

(2)识别当前的 shell 版本

可用下面的方法获知当前使用的是哪种 shell

echo $SHELL

也可用

echo $0

例:

[root@localhost ~]# echo $SHELL
/bin/bash
[root@localhost ~]# echo $0
-bash

(3)检查当前是否为超级用户

UID 是一个重要的环境变量,可用于检查当前脚本是以超级用户还是普通用户身份运行的。

root 用户的 UID 是0

例:

#!/bin/bash
# 文件名: checkroot.sh

if [ $UID -ne 0 ]; then   # -ne  不等于
echo Non root user. Please run as root.
else
echo "Root user"
fi

(4)修改 Bash 提示字符串

打开一个终端或是运行一个 shell,都会看到类似于 user@hostname: /home/$ 的提示字符串。

不同 GNU/Linux 发布版中的提示及颜色也略有不同。

可用 PS1 环境变量来定制提示文本。

查看当前 PS1 的值:

[root@localhost ~]# echo $PS1
[\u@\h \W]# 

有些特殊字符可扩展成系统参数:

\u 用户名;\h 主机名;\w 当前工作目录。

修改 PS1 查看效果:

[root@localhost ~]# PS1="\u@\h \W $ INPUT> "
root@localhost ~ $ INPUT>

可以利用类似 \e[1;31 的特定转义序列来设置彩色的提示字符串:

[root@localhost ~]# PS1="[\e[1;31m\u\e[0m@\h \W]# "

效果如下:

image

若要永久生效,将其追加到 ~/.bashrc 文件中即可。

1.4 算术操作

1.4.1 整数运算

shell 环境中,可用 let(( ))[] 执行基本的算术操作

  • let

    当使用 let 时,变量名之前不需要再添加 $.

    加、减、乘、除:

    #!/bin/bash
    # 文件名 let.sh
    no1=1;
    no2=2;
    let result=no1+no2;
    echo $result;
    let result=no1-no2;
    echo $result;
    let result=no1*no2;
    echo $result;
    let result=no1/no2;
    echo $result;
    

    结果:

    image

    自加、自减

$ let no1++

$ let no1--

简写

let no+=6    <=>   let no=no+6
let no-=6    <=>   let no=no-6
  • ****[ ]

[]let 命令类似

result=$[ no1 + no2 ]

[] 中也可以使用 $ 前缀

result=$[ $no1 + 5 ]
  • (( ))

也可以使用 (())

result=$(( no1 + 50 ))

(()) 中也可以使用 $ 前缀

result=$(( $no1 + 50 ))
  • expr

expr 同样可以用于基本算术操作:

result=`expr 3 + 4` 
result=$(expr $no1 + 5)
# `` 和 $() 效果相同,都能将结果赋值。

注意:

使用 expr 进行计算,两个操作数之间必须要有空格。

以上这些方法只能用于整数运算,而不支持浮点数。

1.4.2 浮点数运算

借助 bc 执行浮点数运算并应用一些高级函数。


echo "4 * 0.56" | bc 
2.24  

no=54;  
result=`echo "$no * 1.5" | bc` 
echo $result 
81.0

其他参数可以置于要执行的具体操作之前,同时以分号作为定界符,通过 stdin 传递给 bc

  • 设定小数精度

参数 scale 可用于指定保留的小数位数:

echo "scale=2;3/8" | bc
0.37   # 精确结果为0.375,保留两位是0.37,不是四舍五入
  • 进制转换

bc 参数将一种进制系统转换为另一种。

obase :指定要转换为何进制。

ibase :指明操作数为何进制,默认为十进制。

#!/bin/bash 
# 用途:进制转换 

no=100 
echo "obase=2;$no" | bc 

no=1100100
echo "obase=10;ibase=2;$no" | bc

结果:

1100100
100
  • 平方以及平方根
echo "sqrt(100)" | bc # 平方根
echo "10^10" | bc # 平方

1.4.3 补充内容

常用函数 说明
length() 返回数据的位数
scale() 返回数据小数位数
s(x)、c(x)、 a(x) 正弦、余弦、反正切函数(x为弧度)
sqrt() 开平方
l(x) 自然对数函数
e(x) 以e为底的指数函数
j(n,x) 贝赛尔函数

​ 使用这些库函数时,bc 要加上 -l 参数

echo "l(10)" | bc -l           # 实现自然对数运算,即ln10
2.30258509299404568401

1.5 文件描述符和重定向

1.5.1 文件描述符

文件描述符是用于访问文件的一个抽象指针。存取文件离不开被称为“文件描述符”的特殊数字。01 2 分别是stdinstdout stderr 的预留描述符。

文件描述符是一个整数,与输入、输出相关联,用来跟踪已打开的文件。还可将某个文件描述符的内容重定向到另一个文件描述符中。常见的文件描述符:

  • 标准输入(stdin) —— 0
  • 标准输出(stdout) —— 1
  • 标准错误(stderr) —— 2

当命令输出文本时,有可能是错误信息,也可能是正常的(非错误的)信息。单看输出文本,无法区分是。可用文件描述符解决该问题,将那些与特定描述符关联的文本提取出来。结合重定向操作符使用。

1.5.2 重定向

重定向(覆盖)

$ echo "This is a sample text 1" > temp.txt

" > " 会先清空目标文件 temp.txt 中内容,再将输出文本存储到 temp.txt

重定向(追加)

$ echo "This is sample text 2" >> temp.txt

" >> " 会将文本追加到目标文件中。

使用重定向操作符,内容不会出现在终端,而是直接被导入文件。重定向操作符默认使用标准输出。即 > 等同于1>>> 等同于 1>> 。若要用特定的文件描述符,必须将描述符置于操作符之前。

重定向标准错误输出:

$ ls +        # "+ 是一个非法参数,因此将返回错误信息
ls: cannot access +: No such file or directory

成功和不成功的命令

当一个命令发生错误并退回时,它会返回一个非0的退出状态;

而当命令成功完成后,它会返回数字0。

退出状态可以从特殊变量 $? 中获得(在命令执行语句之后立刻运行echo $?,就可以打印出退出状态)。

以上命令是将 stderr 文本打印到屏幕上,使用标准错误文件描述符 2 可将 stderr 重定向:

$ ls + 2> out.txt # 正常运行,无输出

可将 stderr 单独重定向到一个文件,将 stdout 重定向到另一个文件:

$cmd 2>stderr.txt 1>stdout.txt

还可将 stderr 转换成 stdout ,使得 stderrstdout 都被重定向到同一个文件中:

$ cmd> output.txt 2>&1

或采用以下形式:

$ cmd &> output.txt

有时,输出中包含一些不必要的信息(比如除错信息)。如果不想让其打印在终端中,可将 stderr 的输出重定向到 /dev/null 丢弃。

例:假设我们有三个文件,分别是 a1a2a3 。但普通用户对文件 a1 没有 “读、写、执行” 的权限。使用 cat 命令打印文件名以 a 起始的所有文件的内容。

设置一些测试文件:

$ echo a1 > a1 
$ cp a1 a2 ; cp a2 a3;
$ chmod 000 a1  # 清除所有权限

cat

$ cat a*
cat: a1: Permission denied
a1
a1

其中,cat: a1: Permission denied 属于 stderr 。我们可以将 stderr 信息重定向到一个文件中,而 stdout 仍然保持不变。

$ cat a* 2> err.txt # stderr被重定向到err.txt
a1
a1
$ cat err.txt
cat: a1: Permission denied

也可将 stderr 信息丢弃:

cat a* 2> /dev/null # 来自stderr的输出被丢到文件/dev/null中
a1
a1

/dev/null 是一个特殊的设备文件,这个文件接收到的任何数据都会被丢弃。因此,null 设备通常也被称为位桶( bit bucket )或黑洞。

当对 stderr stdout 进行重定向时,重定向的文本将传入文件。因为文本已经被重定向到文件中,也就没剩下什么东西可以通过管道(|)传给接下来的命令,而这些命令是通过stdin来接收文本的

使用命令 tee 可将数据重定向到文件,另外还提供一份重定向数据的副本作为后续命令的 stdin

command | tee FILE1 FILE2
$ cat a* | tee out.txt | cat -n
cat: a1: Permission denied
     1 a1
     2 a1
# 命令cat -n 从stdin中接收到的每一行数据前加上行号并写入stdout,注意,错误输出没标行号

查看 out.txt 的内容:

$ cat out.txt
a1
a1

注意,cat: a1: Permission denied 并没有在文件内容中出现。这是因为这些信息属于stderr,而tee只能从stdin中进行读取。

tee 默认会将文件覆盖,使用 -a 选项,可以用于追加内容。

$ cat a* | tee -a out.txt | cat -n

可用 stdin 作为命令参数。只需将 - 作为命令的文件名参数即可:

$ cmd1 | cmd2 | cmd -

例: tee 重定向到 - 即标准输入,这样终端就打印了两份。

$ echo who is this | tee -
who is this
who is this

或者也可以将 /dev/stdin 作为输出文件名来使用 stdin

类似地,使用 /dev/stderr 代表标准错误,/dev/stdout 代表标准输出。这些特殊的设备文件分别对应stdinstderrstdout

image

1.5.3 补充内容

(1)将文件内容重定向到命令

$ cmd < file

(2)重定向脚本内部的文本块

cat <<EOF 会等待输入,知道 EOF标识符为止。

$ cat <<EOF
>

利用EOF可在脚本内对文本块进行重定向:

#!/bin/bash
cat <<EOF>log.txt
LOG FILE HEADER
This is a test log file
Function: System statistics
EOF

cat <<EOF>log.txt 与下一个 EOF 行之间的所有文本行都会被当做 stdin 数据。

log.txt 内容打印如下:

$ cat log.txt
LOG FILE HEADER
This is a test log file
Function: System statistics

(3)自定义文件描述符

可用 exec 命令创建自定义的文件描述符。

文件通常有 3 种打开模式:

  • 只读模式
  • 截断模式
  • 追加模式

< 操作符用于从文件中读取至 stdin

> 操作符用于截断模式的文件写入(数据在目标文件内容被截断之后写入)。

>> 操作符用于追加模式的文件写入(数据被添加到文件的现有内容中,而且该目标文件中原有的内容不会丢失)。

文件描述符可以用以上三种模式中的任意一种来创建。

为读取文件创建一个文件描述符:

$ echo this is a test line > input.txt
$ exec 3<input.txt

使用自定义的文件描述符3

$ cat <&3
this is a test line

自定义的文件描述符只能使用一次,如果要再次读取,就不能再继续使用文件描述符 3 了,而是要用 exec 重新分配文件描述符 3 以便用于读取。

创建一个文件描述符用于写入(截断模式):

$ exec 4>output.txt
$ echo newline >&4
$ cat output.txt
newline

创建一个文件描述符用于写入(追加模式):

$ exec 5>>input.txt
$ echo appended line >&5
$ cat input.txt
newline
appended line

理解:文件描述符是用于访问文件的一个抽象指针。

1.6 数组和关联数组

数组是 shell 脚本非常重要的组成部分,它借助索引将多个独立的数据存储为一个集合。

1.6.1 普通数组

普通数组只能使用整数作为数组索引。

定义一个数组(方法1):

array_var=(1 2 3 4 5 6)
# 这些值将会存储在以0为起始索引的连续位置上

定义一个数组(方法2):

array_var[0]="test1"
array_var[1]="test2"
array_var[2]="test3"
array_var[3]="test4"
array_var[4]="test5"
array_var[5]="test6"

列出数组索引

echo ${!array_var[*]}
0 1 2 3 4 5

另一种形式:

echo ${!array_var[@]}
0 1 2 3 4 5

打印出特定索引的数组元素内容:

$ echo ${array_var[0]}
test1

index=5

$ echo ${array_var[$index]}
test6

以清单形式打印出数组中的所有值:

$ echo ${array_var[*]}
test1 test2 test3 test4 test5 test6

另一种形式:

$ echo ${array_var[@]}
test1 test2 test3 test4 test5 test6

打印数组长度(即数组中元素的个数):

$ echo ${#array_var[*]}
6

1.6.2 关联数组

在关联数组中,可以用任意的文本作为数组索引。

声明一个关联数组:

$ declare -A ass_array

添加元素(方法1):

$ ass_array=([index1]=val1 [index2]=val2)

添加元素(方法2):

$ ass_array[index1]=val1
$ ass_array[index2]=val2

例:如何用关联数组为水果制订价格?

$ declare -A fruits_value
$ fruits_value=([apple]='100dollars' [orange]='150 dollars')  #值有空格,用引号引起来

列出数组索引

echo ${!fruits_value[*]}
orange apple

另一种形式:

echo ${!fruits_value[@]}
orange apple

打印出特定索引的数组元素内容:

$ echo ${fruits_value[orange]}
150 dollars

以清单形式打印出数组中的所有值:

$ echo ${fruits_value1[*]}
150 dollars 100dollars

删除数组某个元素

unset ass_array[index]

删除整个数组

unset ass_array

1.7 别名

别名有多种实现方式,可以使用函数,也可以使用 alias 命令。

1.7.1 创建别名

alias 命令能够为任何重要的命令创建别名。

创建别名:

$ alias new_command='command sequence'

为安装命令yum install创建别名:

$ alias install='yum install'

这样就可以用install packgename 代替yum install了。

别名永久生效:

$ echo 'alias cmd="command seq"' >> ~/.bashrc
$ source ~/.bashrc

删除别名:

$ unalias install

或将对应的定义别名语句从 ~/.bashrc中删除。

例:创建一个别名 rm ,它能够删除原始文件,同时在 backup 目录中保留副本。

$ alias rm='cp $@ ~/backup; rm $@'

以上这条命令需在ubuntu低版本的系统才支持,现在基本上都不支持了,会报错 cp 命令缺少文件操作数。也就是只有后面的 rm 命令获得了所有参数 $@ ,要解决这个问题可使用函数,如下:

$ alias rm='copy1(){ [ -d ~/rmbackup ] || mkdir ~/rmbackup;cp -a $@ ~/rmbackup/;rm $@; };copy1 $@'
$ alias rm='move1(){ [ -d ~/rmbackup ] || mkdir ~/rmbackup;mv -f $@ ~/rmbackup; };move1 $@'      # 用这个别名,不要再用rm -rf参数

因为执行别名时的参数只能传递给最后一个命令即 copy1move1 函数,但 "$@" 代表的参数可以传递给函数,让函数中的 "$@" 得到正确的扩展,于是整个别名都能合理且正确地执行。

1.7.2 补充内容

有时候未必总是希望使用这些别名,因为别名也会造成安全问题。

攻击者可能利用别名将某些特权命令替换成了一些别有用心的命令,借此来盗取用户输入的重要信息。

这时候我们可以将所要运行的命令进行转义,从而忽略当前定义过的所有别名:

$ \command

字符 \ 对命令实施转义,使我们可以执行原本的命令,而不是这些命令的别名替身。在不信任的环境下执行特权命令,通过在命令前加上 \ 来忽略可能存在的别名设置总是一个不错的安全实践。

1.8 获取终端信息

1.8.1 tput

当我们放缩终端大小时,行数和列数会发生变化。

使用 tput 获取当前终端的行数和列数:

$ tput lines                  # 打印出当前终端共有多少行
$ tput cols                   # 打印出当前终端共有多少列

打印出当前终端名:

$ tput longname

移动光标到指定坐标处:

tput cup x坐标 y坐标

例:光标移动到当前终端第三行第五列。

$ tput cup 3 5

效果:

image

设置终端背景色:

tput setb no

其中,no 可以在 07 之间取值。颜色的对应关系如下,可能会因 UNIX 系统的不同而异:

  • 0:黑色
  • 1:蓝色
  • 2:绿色
  • 3:青色
  • 4:红色
  • 5:洋红色
  • 6:黄色
  • 7:白色

例:设置背景颜色为红。

[root@localhost ~]# tput setb 4

效果:

image

设置终端前景色:

$ tput setf no

例:设置前景颜色为红。

[root@localhost ~]# tput setf 4

效果:

image

可以看到字体颜色变成了红色。

反显当前的配色方案:即前景色和背景色互换

$ tput rev

效果:

image

设置文本样式为粗体:

$ tput bold

设置下划线的开闭:

$ tput smu1
$ tput rmu1

效果:

image

存储光标位置

$ tput sc

恢复光标位置

$ tput rc

删除当前光标位置到行尾的所有内容:

$ tput ed

清屏:

$ tput clear           # 等同于clear命令

设置光标可见和不可见:

$ tput civis           # 光标隐藏
$ tput cnorm           # 光标可见

重置终端设置:

$ tput reset

注意,PS1环境变量修改过可能导致tput的修改都不会生效。

1.8.2 stty

输入密码的时候,为了不让输入的内容显示出来可用 stty

选项 -echo 禁止将输出发送到终端

选项 echo 允许发送输出:

#!/bin/sh
# Filename: password.sh
echo -e "Enter password: "
stty -echo
read password
stty echo
echo
echo Password read.

1.9 时间

UNIX系统中,日期被存储为一个整数,其大小为自世界标准时间 1970年1月1日0时0分0秒起所流逝的秒数。这种计时方式称之为纪元时UNIX时间

1.9.1 格式化打印时间

校时:

[root@localhost ~]# ntpdate ntp.ntsc.ac.cn   # 中国科学院国家授时中心
 9 Feb 13:50:17 ntpdate[7307]: step time server 114.118.7.161 offset -28799.485867 sec

读取系统当前时间:

$ date
Thu Feb  9 13:51:13 CST 2023

打印纪元时:

$ date +%s
1675954398   # 从世界标准时间1970年1月1日0时0分0秒起至当前时刻的总秒数,不包括闰秒

日期串转纪元时:

$ date --date "Thu Feb  9 13:51:13 CST 2023" +%s
1675921873

选项 --date用于提供日期串作为输入,我们可以使用任意的日期格式化选项来打印输出。

例:打印出给定日期串是星期几。

$ date --date "Thu Feb  9 13:51:13 CST 2023" +%A
Thursday

下表是date命令所支持的格式选项

日期内容 格  式
星期 %a(例如:Sat)
%A(例如:Saturday)
%b(例如:Nov)
%B(例如:November)
%d(例如:31)
固定格式日期(mm/dd/yy) %D(例如:10/18/10)
%y(例如:10)
%Y(例如:2010)
小时 %I或 %H(例如:08)
分钟 %M(例如:33)
%S(例如:10)
纳秒 %N(例如:695208515)
UNIX纪元时(以秒为单位) %s(例如:1290049486)

利用格式串选项结合 + 作为 date 命令的参数,可以要求打印出对应格式的日期。

$ date "+%d %B %Y"
09 February 2023

手动设置日期:

[root@localhost ~]# date -s "格式化的日期字符串"

例:

[root@localhost ~]# date -s "2024-2-9 15:30:30"
Fri Feb  9 15:30:30 CST 2024

检查一组命令所花费的时间:

#!/bin/bash
# 文件名: time_take.sh
start=$(date +%s)
commands;
statements;

end=$(date +%s)
difference=$(( end - start))
echo Time taken to execute commands is $difference seconds.

1.9.2 补充内容

在脚本中生成延时

可以使用 sleep

$ sleep no_of_seconds

例:0-40计时。

echo -n 输出不换行

tput sc存储光标位置

tput rc恢复光标位置

#!/bin/bash
# Filename: sleep.sh
echo -n Count:
tput sc

count=-1;
while true;
do
if [ $count -lt 60 ];
then let count++;
echo -n $count;
sleep 1;
tput rc
tput ed
else exit 0;
fi
done

1.10 调试脚本

Bash本身包含了一些选项,能够打印出脚本接受的参数和输入。

选项 -x,启动跟踪调试 shell 脚本:

$ bash -x script.sh

运行带有 -x 标志的脚本能打印出所执行的每一行命令以及当前状态。

-x 标识将脚本中执行过的每一行都输出到 stdout

  • set -x: 在执行时显示参数和命令。
  • set +x: 禁止调试。
  • set -v: 当命令进行读取时显示输入。
  • set +v: 禁止打印输入。

利用 set -xset +x 限制调试区域

#!/bin/bash
# 文件名: debug.sh

for i in {1..6}
do
set -x
echo $i
set +x
done
echo "Script executed"

在上面的脚本中,仅在 -x+x 所限制的区域内,echo $i 的调试信息才会被打印出来。

例:自定义调试信息

#!/bin/bash
function DEBUG()
{
[ "$_DEBUG" == "on" ] && $@ || :   # 若_DEBUG为on就获取并打印所有参数,否者什么都不做
}

for i in {1..10}
do
DEBUG echo $i 
done

&& 左边的命令返回真(即返回 0,成功被执行)后,&& 右边的命令才能够被执行;换句话说,“如果这个命令执行成功 && 那么执行这个命令”。

|| 则与 && 相反。如果 || 左边的命令未执行成功,那么就执行 || 右边的命令;或者换句话说,“如果这个命令执行失败了 || 那么就执行这个命令。

可以将调试功能置为 on 来运行上面的脚本:

$ _DEBUG=on ./script.sh

在需要打印调试信息的语句前加上 DEBUG 。如果没有把 _DEBUG=on 传递给脚本,那么调试信息就不会打印出来。在 Bash 中。

$@ 获取所有参数并打印

: 令shell不要进行任何操作

例:利用shebang来进行调试

#!/bin/bash -xv,
# 文件名: shebang.sh
commands;
$ ./shebang.sh

1.11 函数和参数

定义函数:

function fname()
{
statements;
}

fname()
{
statements;
}

只需要使用函数名就可以调用某个函数:

$ fname ; # 执行函数

访问函数参数的方法:

fname()
{
  echo $1,$2;  # 访问参数1和参数2
  echo "$@";   # 以列表的方式一次性打印所有参数
  echo "$*";   # 类似于$@,但是参数被作为单个实体
  return 0;    # 返回值
}
fname arg1 arg2

也给脚本传递参数

$ ./script.sh  arg1 arg2
#!/bin/bash 
# 文件名: script.sh
echo $1 $2
  • $1 是第一个参数。$2 是第二个参数。$n 是第 n 个参数。10 以上需要大括号,如 ${10}
  • "$@" 被扩展成 "$1" "$2" "$3" 等。
  • "$*" 被扩展成 "$1c$2c$3",其中 cIFS 的第一个字符。
  • 没有被双引号时 $@$* 相同。"$@" 用得最多。由于 "$*"将所有的参数当做单个字符串,因此它很少被使用。
  • $# 代表命令行中所有参数的个数。
  • $0 是指当前shell脚本本身的名字。

递归调用:

Bash中,函数同样支持递归:

#!/bin/bash 

Fname() 
{ 
	echo $1; 
	sleep 1;
	Fname hello; 
 }
 Fname hello

Fork 炸弹:

:(){ :|:& };:     # 这个函数会一直地生成新的进程,最终形成拒绝服务攻击

可修改 /etc/security/limits.conf 中的 nproc 来限制可生成的最大进程数,阻止这种攻击。

下面的语句将所有用户可生成的进程数限制为100

hard nproc 100

导出函数:

函数也能像环境变量一样用 export 导出,如此一来,函数的作用域就可以扩展到子进程中:

export -f 选项代表[变量名称]中为函数名称

image

命令返回值(状态):

保存在变量 $? 中,如果命令成功退出,那么 $?0,否则为非 0

#!/bin/bash
# 文件名: success_test.sh
CMD="command"    # command指代你要检测退出状态的目标命令
$CMD
if [ $? -eq 0 ];
then
echo "$CMD executed successfully"
else
echo "$CMD terminated unsuccessfully"
fi

向命令传递参数

$ cat showArgs.sh
for i in `seq 1 $#`
do
echo $i is $1
# shift 命令可以将参数依次向左移动一个位置,让脚本能够使用 $1 来访问到每一个参数
shift
done
$ sh showArgs.sh a b c
1 is a
2 is b
3 is c

补充内容:

seq 命令用于输出固定间隔的数字。

seq 连续输出:

seq 3   # 表示连续输出1到3
seq 2 4 # 表示连续输出2到4

步进输出,间隔为2,最大到10:

seq 1 2 10

常用选项:

-s  # 指定输出的分隔符,默认为\n,即默认为回车换行
-w  # 指定为定宽输出,不能和-f一起用
-f  # 按照指定的格式输出,不能和-w一起使用

# 指定加号为分隔符,输出的数字将会使用"+"连接,默认情况下回车换行(\n)为分隔符。
seq -s +  10
1+2+3+4+5+6+7+8+9+10

使用制表符(\t)作为分隔符,相当于我们键盘上的tab键。
seq -s "`echo -e "\t"`"  2 2 10
2	4	6	8	10
# 注意:使用了命令替换,也就是说,先使用echo命令输出制表符,然后用输出的制表符作为seq命令输出数字的连接符。

# -w 以最大值的位数为标准宽度,不足标准宽度的数字将会用0补位
seq -w 7 9
7
8
9
# seq -w 8 10
08
09
10

# "%3g"这种格式表示指定"位宽"为三位,那么数字位数不足部分用空格补位
seq -f '%3g' 8 10
  8
  9
 10
# %02g" 表示指定位宽为两位,数字位数不足用0补位
seq -w 8 10
08
09
10
# % 前面还可以指定字符串。
seq -f 'dir%g' 1 3
dir1
dir2
dir3
# 例如:一次性创建5个名为dir001 , dir002 .. dir005 的目录。
mkdir $(seq -f 'dir%03g' 1 10)

# 或者如下命令,与上述命令的效果相同。

seq -f 'dir%03g' 1 5 | xargs mkdir

1.12 读取命令输出

输出传递

cmd1 的输出传递给 cmd2,而 cmd2 的输出传递给 cmd3

$ cmd1 | cmd2 | cmd3

例1:

$ ls | cat -n > out.txt

子shell

()操作符来定义一个子 shell 。当命令在子 shell 中执行时,不会对当前 shell 造成任何影响;所有的改变仅限于该子 shell 内。例如,当用 cd 命令改变子 shell 的当前目录时,这种变化不会反映到主shell环境中

用子 shell 将命令输出存储到变量:

cmd_output=$(COMMANDS)

cmd_output=`COMMANDS`

例:

pwd;
(cd /bin; ls);
pwd;

执行这个脚本不会改变当前所在目录。

通过引用子 shell 的方式保留空格和换行符。

$ out=$(cat /etc/passwd)

$ echo $out                                     # 回车丢失了

$ echo "$out"                                   # 回车未丢失

1.13 read 命令

① 从输入中读取 n 个字符并存入变量 variable_name

read -n 需要读取字符的数量  变量名

例如:
read - n 2 var
echo $var

② 用无回显的方式读取密码:

read -s var

③ 使用 read 显示提示信息:

read -p "Enter input:" var

④在给定时限内读取输入:

read -t timeout var
例如:
# 在两秒内将键入字符串读入变量var
read -t 2 var

⑤用特定的定界符作为输入行的结束:

read -d delim_char var

例如
read -d ":" var

⑥屏蔽文本中的特殊符号,只做输出不做转译:

read -r var

1.14 字段分隔符和迭代器

1.14.1 字段分隔符IFS

IFS 存放内部字段分隔符的环境变量。

IFS 默认值为空白字符串(换行符,制表符或者空格)

例:

data="name,sex,rollno,location"  
# 我们可以使用IFS读取变量中的每一个条目 
oldIFS=$IFS 
IFS=,  # 将IFS设置为逗号 
for item in $data; 
do
echo Item: $item 
done 
IFS=$oldIFS

利用 IFS打印出用户以及他们默认的 shell

#!/bin/bash
# 用途: 演示IFS的用法

line="root:x:0:0:root:/root:/bin/bash"
oldIFS=$IFS;
IFS=":"
count=0
for item in $line;
do

[ $count -eq 0 ] && user=$item;
[ $count -eq 6 ] && shell=$item;
let count++
done;
IFS=$oldIFS
echo $user\'s shell is $shell;

1.14.2 循环语句

for循环

①面向列表的 for 循环

# list可以是一个字符串,也可以是一个值序列
for var in list;
do
  statements;   # 使用变量$var
done

# 例如
for i in {a..z};do actions; done;
# echo {a..z}或{A..Z},或是使用{1..50}生成部分列表

②迭代指定范围的数字

for (( 初始值;循环控制条件;变量变化 ))
    do
        程序
    done

注意:
  ①初始值:在循环开始时,需要给某个变量赋予初始值,如i=1
  ②循环控制条件//;用于指定变量循环额次数,如i<=100,则只要i的值小于等于100,循环就会继续
  ③变量变化:每次循环之后,变量该如何变化,如i=i+1.代表每次循环之后,变量i的值都加1

while 循环

循环条件满足时退出循环,用 true 或者 : 作为循环条件能够产生无限循环。

while condition
do
commands;
done
#!/bin/bash

i=1
s=0

while [ $i -le 100 ]
        do
                s=$(( $s+$i ))
                i=$(( $i+1 ))
        done
echo "The Num is:$s"
使用read结合while循环读取文本文件:

示例代码1:

#!/bin/bash
 # 将位置参数1的文件名复制给file
file=$1   
 # 判断用户是否输入了位置参数
if [ $# -lt 1 ];then     
  echo "Usage:$0 filepath"
  exit
fi
# 从file文件中读取文件内容赋值给line(使用参数r会屏蔽文本中的特殊符号,只做输出不做转译)
while read -r line   
do
# 输出文件内容
  echo $line        

done   <  $file

#!/bin/bash
file=$1
if [[ $# -lt 1 ]]
then
  echo "Usage: $0 please enter you filepath"
  exit
fi
# 将文件内容分为三列
while read -r  f1 f2 f3    
do
  # 按列输出文件内容
  echo "file 1:$f1 ===> file 2:$f2 ===> file 3:$f3" 

done < "$file"


效果如下图所示:

image

until 循环

util 会一直循环,直到条件为真

x=0;
until [ $x -eq 9 ];         
do let x++; echo $x;
done 

repeat

持续运行命令直至成功。

说明:函数 repeat() 中包含一个无限 while 循环,该循环执行以函数参数形式(通过 $@ 访问)传入命令,如果命令执行成功,则返回,进而退出循环。

repeat()
{
  while true
  do
     $@ && return
  done     
}

或:

repeat() {while true; do $@ && return; done }

另一种方式:

repeat() {while :; do $@ && return; done }

true 每执行一次 while 循环就生成一个进程,而 命令退出状态总是 0,且不会生成进程,效率更高。

加入延时:

repeat() {while :; do $@ && return;slepp 30; done }

例: ls -l 查看一个文件的详细信息,该文件不存在是命令一直执行,当创建该文件后命令执行成功退出循环。

repeat()
{
  while true
  do
     $@ && return
     sleep 1
  done
}
repeat ls -l tesh.txt

执行后创建该文件循环结束:

[root@localhost ~]# sh test.sh
ls: cannot access tesh.txt: No such file or directory
ls: cannot access tesh.txt: No such file or directory
ls: cannot access tesh.txt: No such file or directory
-rw-r--r--. 1 root root 0 Feb 13 18:03 tesh.txt

1.15 比较与测试

1.15.1 if 语句

语法:

# if条件

if condition;
then
commands;
fi
# else if 和 else

if condition;
then
commands;
elif condition;
then
    commands
else
    commands
fi

利用逻辑运算符简化 if 语句:

[ condition ] && action;		 # 如果condition为真,则执行action
[ condition ] || action; 		 # 如果condition为假,则执行action

1.15.2 算术比较

条件通常被放置在封闭的中括号内。一定要注意在[]与操作数之间有一个空格。如果忘记了这个空格,脚本就会报错。

image

$ [ 22 -ne 33 ] && echo "yes" || echo "no"
yes

1.15.3 文件判断

按照文件类型进行判断

image

image

$ [ -d /root/sh ] && echo "yes" || echo "no"
no

按照文件权限进行判断

image

$ [ -w student.txt ] && echo "yes" || echo "no"
yes

两个文件之间进行比较

image

$ ln /root/student.txt /tmp/stu.txt 		# 创建硬链接

$ [ /root/student.txt -ef /tmp/stu.txt ] && echo "yes" || echo "no"
yes

1.15.4 字符串判断

image

$ [ "$aa" == "$bb" ] && echo "yes" || echo "no"
no
# 也可使用 [ "$aa" = "$bb" ] , = 两边要有空格,没空格是赋值。

1.15.5 多重条件判断

image

$ [ -n "$aa" -o "$aa" -gt 23 ]&& echo "yes" || echo "no"
yes

或者使用逻辑运算符 && 和 || 将多个条件组合起来:

if [[ condition1 ]] && [[ condition2 ]] ;
then
commands;
fi
str1="Not empty "
str2=""
if [[ -n $str1 ]] && [[ -z $str2 ]];
then
echo str1 is non-empty and str2 is empty string.
fi

输出如下:

str1 is non-empty and str2 is empty string.

第2章 命令之乐

2.1 cat 拼接

cat 本身表示 concatenate(拼接)。

基本语法:

cat file1 file2 ....

该命令将作为命令行参数的内容文件拼接在一起并将结果发送到 stdout

[root@test ~]# cat txt1.txt txt2.txt
txt1
txt2

从标准输入中读取:

OUTPUT_FROM_SOME COMMANDS | cat

将输入文件的内容与标准输入拼接:

[root@test ~]# echo 'Text through stdin' | cat - txt1.txt
Text through stdin
txt1

- 被作为来自stdin文本的文件名

显示行号:

[root@test ~]# cat -n txt3.txt
     1  1
     2
     3  3
     4
     5
     6  6

压缩空白行:

[root@test ~]# cat -sn txt3.txt
     1  1
     2
     3  3
     4
     5  6

标记制表符:

$ cat file.py
def function():
    var = 5
        next = 6
    third = 7
 
$ cat -T file.py
def function():
^Ivar = 5
        next = 6
^Ithird = 7^I

2.2 录放终端会话

录制:

$ script -t 2> timing.log -a output.session
type commands;
…
..
exit

timing.log 用于存储时序信息,描述每一个命令在何时运行;output.session 用于存储命令输出。

-t 选项用于将时序数据导入 stderr2> 则用于将 stderr 重定向到 timing.log

回放:

借助这两个文件:timing.log(存储时序信息)和 output.session (存储命令输出信息)进行回放:

$ scriptreplay timing.log output.session           # 这两个文件名字随意

广播

广播员先执行:

$ mkfifo scriptfifo

听众执行:

$ cat scriptfifo

广播员再执行:

$ script -f scriptfifo
$ commands;

2.3 find 文件查找

find 命令的工作方式:沿着文件层次结构向下遍历,匹配符合条件的文件,并执行相应的操作。

默认的操作是打印出文件和目录。

列出当前目录及子目录下所有的文件和文件夹:

$ find base_path

可使用print 选项使用 \n (换行符)分隔输出的每个文件或目录名。-print0 选项则使用空字符'\0' 来分隔。

根据文件名进行搜索:

$ find /home -name "*.txt" -print

选项 -iname 作用和 -name 类似,不过在匹配名字的时候会忽略大小写。

[root@test test]# ls
2.txt  2.TxT
[root@test test]# find . -iname *.TXT
./2.txt
./2.TxT

直接逻辑操作符 -o-a ,使用\( \) 将逻辑语句视为一个整体。如果不使用\(\)而且该语句后面还有参数,可能出现难以预料的情况。

[root@test test]# ls
2.txt  2.TxT  a.pdf
[root@test test]# find . \( -name "*.txt" -o -name "*.pdf" \)
./2.txt
./a.pdf
[root@test test]# ls
2.txt  2.TxT  a.pdf  b.pdf
[root@test test]# find . \( -name "*a*" -a -name "*p*" \)
./a.pdf

-path -regex匹配路径

[root@test test]# mkdir -p dir1/dir2/linux/Centos7
[root@test test]# touch dir1/dir2/linux/Centos7/1.txt
[root@test test]# touch dir1/dir2/linux/2.txt
[root@test test]# find  dir1/ -path "*Centos*"
dir1/dir2/linux/Centos7
dir1/dir2/linux/Centos7/1.txt
[root@test test]# find  dir1/ -path '*Centos*' -name '*.txt*' -print
dir1/dir2/linux/Centos7/1.txt
$ ls
new.PY next.jpg test.py script.sh
$ find . -regex ' .*\.(py\|sh\)$ '          # 匹配.py或.sh文件
./test.py
script.sh
-iregex 选项可以让正则表达式在匹配时忽略大小写。例如:
$ find . -iregex ' .*\(\.py\|\.sh\)$ '
./test.py
./new.PY
./script.sh

否定参数 !

# 除了.txt结尾的其余都显示
[root@test ~]# find . ! -name '*.txt'

基于目录深度搜索

-maxdepth 参数指定最大深度

$ find . -maxdepth 1 -type f -print
# 该命令只列出当前目录下的所有普通文件。即使有子目录,也不会被打印或遍历

-mindepth 参数指定最小深度

$ find . -mindepth 2 -type f -print
./dir1/dir2/file1
./dir3/dir4/f2
# 即使当前目录或dir1和dir3中包含有文件,它们也不会被打印出来

-maxdepth-mindepth 应该在 find 命令中及早出现。如果作为靠后的选项,有可能会影响到 find 的效率,因为它不得不进行一些不必要的检查。例如如果 -maxdepth 出现在 -type 之后, find 首先会找出 -type 所指定的文件,然后再在匹配的文件中过滤掉不符合指定深度的那些文件。但是如果反过来,在 -type 之前指定目录深度,那么 find 就能够在找到所有符合指定深度的文件后,再检查这些文件的类型,这才是最有效的搜索之道。

根据文件类型搜索
image

# 只列出目录
find . -type d -print

根据文件的时间戳进行搜索

find 命令的时间戳操作处理选项对编写系统备份和维护脚本很有帮助

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

Unix 默认并不保存文件的创建时间。但有一些文件系统( ufs2ext4 zfsbtrfsjfs )会选择这么做。可以使用 stat 命令访问文件创建时间

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

-mtime 选项会根据修改时间展开搜索, -ctime 会根据变化时间展开搜索。
-atime -mtime 以及 -ctime 都是以“天”为单位来计时的。

find 命令还支持以“分钟
为计时单位的选项。这些选项包括:

  • -amin (访问时间)
  • -mmin (修改时间)
  • -cmin (变化时间)
# 打印出7分钟之前访问的所有文件:
$ find . -type f -amin +7 -print

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

# 找出比file.txt修改时间更近的所有文件

 find . -type f -newer file.txt -print

基于文件大小的搜索

# 找出大于2KB的文件
$ find . -type f -size +2k

# 找出小于2KB的文件
$ find . -type f -size -2k

# 找出大小等于2KB的文件
$ find . -type f -size 2k


除了 k 之外,还可以用其他文件大小单元。

  • b —— 块(512字节)
  • c —— 字节
  • w —— 字(2字节)
  • k —— 千字节
  • M —— 兆字节
  • G —— 吉字节

基于文件权限和所有权的匹配

列出具有特定权限的所有文件:

# 打印出权限为644的文件
$ find . -type f -perm 644 -print
# 打印出权限不是644的文件
$ find . -type f -name "*.php" ! -perm 644 -print

-user USER 能够找出由某个特定用户所拥有的文件,参数 USER 可以是用户名或 UID

$ find . -type f -user rh -print

利用find执行相应操作

① 删除匹配的文件 -delete

$ find . -type f -name '*.sh' -delete

② 执行命令或动作 -exec

clouverd 所拥有的文件都改为 root

$ find . -type f -user clouverd -exec chown root {} \;

-exec 之后可接任何命令,{}表示一个匹配。对于任一个匹配的文件名,{}会被该文件名所替换。

该命令结尾的 \;\为转义符,若不转义,shell 会将其视为 find 命令的结束,而非 chown 命令的结束。

-exec选项只能怪接受单个命令,无法直接使用多个命令,不过可把多个命令写到一个 shell 脚本中,然后在 -exec 中使用这个脚本。

$ -exec ./command.sh {} \;

-exec 能够同 printf 结合来生成有用的输出信息。例如:

$ find . -type f -name "*.txt" -exec printf "Text file: %s\n" {} \;

跳过特定的目录

将某些文件或目录从搜索过程中排除的技术被称为“修剪”,使用 -prune 选项排除某些符合条件的文件:

# -name ".git" –prune 是命令中负责进行修剪的部分,它指明了.git目录应该被排除在外.-type f –print 描述了要执行的操作
$ $ find devel/source_path \( -name ".git" -prune \) -o \( -type f -print \)

排除以 .@ 开头的文件和文件夹:

find /path/to/start/search/ -not -path '*/[@.]*' -type f -mtime -2

排除以 . 开头的文件以及以 .@ 开头的文件和文件夹:

find /path/to/start/search/ -not -path '*/.*' -type f -mtime -2 | grep -v '/@.*/.*'

2.4 xargs

xargs 命令紧跟在管道操作符之后使用标准输入作为主要的数据源,将从 stdin 中读取的数据作为指定命令的参数并执行该命令。

# 将在一组C语言源码文件中搜索字符串main
ls *.c | xargs grep main

① 将多行输入转换成单行输出

[root@test ~]# cat example.txt 
1 2 3 4 5 6
7 8 9 10
11 12
[root@test ~]# cat example.txt | xargs 
1 2 3 4 5 6 7 8 9 10 11 12

② 将单行输入转换成多行输出

-n 选项 限制每次调用命令时用到的参数个数,并非指定输出行数

[root@test ~]# cat example.txt |xargs -n 3
1 2 3
4 5 6
7 8 9
10 11 12

[root@test ~]# cat example.txt |xargs -n 4
1 2 3 4
5 6 7 8
9 10 11 12

③ 指定分割参数的分隔符 . 使用 -d 选项可以为输入数据指定自定义的分隔符

[root@test ~]# echo "splitXsplit2Xsplit3Xsplit4" | xargs -d X
split split2 split3 split4

[root@test ~]# echo "splitXsplit2Xsplit3Xsplit4" | xargs -d X -n 2
split split2
split3 split4

find 命令和 xargs 命令结合一起使用

find 匹配并列出所有的 .txt 文件,然后用 xargs 将这些文件删除:

$ find . -type f -name "*.txt" -print0 | xargs -0 rm -f

这里应当使用 -print0 选项将 \0 作为分隔符,防止文件名中有空格,而造成难以预料的情况。

⑤ 读取 stdin,为命令传入格式化参数

[root@test rh]# cat ceho.sh 
#!/bin/bash

echo $@'#'

[root@test rh]# cat args.txt 
arg1
arg2
arg3

# 限制传入参数的个数
[root@test rh]# cat args.txt | xargs -n 1 ./ceho.sh 
arg1#
arg2#
arg3#

# xargs有一个选项-I,可以用于指定替换字符串,这个字符串会在xargs解析输入时被参数换掉
[root@test rh]# cat args.txt | xargs -I {} ./ceho.sh -p {} -l
-p arg1 -l#
-p arg2 -l#
-p arg3 -l#

注意:使用-I的时候,命令以循环的方式进行,如果有3个参数,那么命令就会连同{}一起被执行3次.{}会在每次执行中被替换为相应的参数

⑥ 统计源代码中所有 C 程序文件的行数

find source_code_dir_path -type f -name "*.c" -print0 | xargs -0 wc –l

⑦ 为多组命令提供参数

xargs 不能为多组命令提供参数,利用一个包含 while 循环的子 shell 可以实现为多组命令提供参数。

$ cat files.txt | ( while read arg; do cat $arg; done )

# 等同于 cat files.txt | xargs -I {} cat {}

while 循环中,可以将 cat $arg 替换成任意数量的命令,这样我们就可以对同一个参数执行多项命令。

2.5 tr

tr translate (转换)的简写,因为它可以将一组字符转换成另一组字符。

tr 只能通过 stdin (标准输入)接收输入(无法通过命令行参数接收)它的调用格式如下:

tr [options] set1 set2         # set1 替换成set2

来自 stdin 的输入字符会按照位置从 set1 映射到 set2 (set1 中的第一个字符映射到 set2 中的第一个字符。以此类推),然后将输出写入 stdout (标准输出)。

set1set2 是字符类或字符组。如果两个字符组的长度不相等,那么 set2 会不断复制其最后一个字符,直到长度与 set1 相同。若 set2 的长度大于 set1 ,那么 set2 中超出 set1 长度的那部分字符则全部忽略。

要将输入中的字符由大写转换成小写,可以使用下面的命令

$ echo "HELLO WHO IS THIS" | tr 'A-Z' 'a-z'
hello who is this

说明:'A-Z' 和 'a-z' 都是字符组。我们可以按照需要追加字符或字符类来构造自己的字符组.'ABD-}' 、 'aA.,' 、 'a-ce-x' 以及 'a-c0-9' 等均是合法的集合。定义集合也很简单,不需要书写一长串连续的字符序列,只需要使用“ 起始字符  终止字符”这种格式就行了。这种写
法也可以和其他字符或字符类结合使用。如果“ 起始字符  终止字符”不是有效的连续字符序列,
那么它就会被视为含有3个元素的集合( 起始字符、  和终止字符)。你也可以使用像 '\t' 、 '\n'
这种特殊字符或其他ASCII字符。

工作原理:在 tr 中利用集合概念,将字符从一个集合映射到另一个集合中。

如下面一个用 tr 进行数字加密和解密的例子:

[root@test ~]# echo 12345 | tr '0-9' '9876543210'
87654 	# 加密
[root@test ~]# echo 87654 | tr '9876543210' '0-9'
12345 	# 解密

tr 可以将制表符转换成单个空格:

tr '\t' ' ' < file.txt

① 用 tr 删除字符

# 只使用set1,不使用set2
[root@test rh]# echo "Hello 123 world 456" |tr -d '0-9'
Hello  world 

② 字符组补集

# 利用选项 -c 来使用 set1 的补集。下面的命令中, set2 是可选的:
tr -c [set1] [set2]
# 会从输入文本中删除不在补集中的所有字符
[root@test rh]# echo hello 1 char 2 next 4 | tr -d -c '0-9 \n'
 1  2  4
# 会将不在 set1 中的字符替换成空格
[root@test rh]# echo hello 1 char 2 next 4 | tr  -c '0-9' ' '
      1      2      4

③ 同 tr 压缩字符

# 删除多余空格
$ echo "GNU is not UNIX. Recursive right ?" | tr -s ' '
GNU is not UNIX. Recursive right ?
# 删除多余换行符
$ cat multi_blanks.txt | tr -s '\n'
line 1
line 2
line 3
line 4

④ 使用技巧:使用 tr 进行算术运算

[root@test rh]# cat sum.txt | echo $[ $(tr '\n' '+' ) 0 ]
15

解析说明:在命令中, tr 命令将 '\n' 替换成了 '+' ,我们因此得到了字符串 1+2+3+..5+ ,但是在字符
串的尾部多了一个操作符 + 。为了抵消这个多出来的操作符,我们再追加一个 0 ,$[ operation ] 执行算术运算,因此就形成了以下命令:echo $[ 1+2+3+4+5+0 ]

如果有一个包含字母和数字的文件,我们想计算其中的数字之和,这需要更强的技巧性:

$ cat test.txt
first 1
second 2
third 3

利用 tr -d 选项删除文件中的字母,然后将回车替换成 +

[root@test ~]# cat test.txt |tr -d [a-z]|echo $[$(tr '\n' '+')]
total: 6

⑤ 字符类

tr 可以将不同的字符类作为集合使用,所支持的字符类如下所示

image

[root@test ~]# echo hello 1 char 2 next 4 | tr '[:lower:]' '[:upper:]'
HELLO 1 CHAR 2 NEXT 4

2.6 校验和

​ 校验和( checksum )程序用来对文件中生成相对较小的唯一的密钥.我们可以重新计算该密钥,用以检查文件是否发生改变。Unix Linux 支持多种校验和程序,但强健性最好且使用最为广泛的校验和算法是 MD5SHA-1md5sumsha1sum 程序可以对数据应用对应的算法来生成校验和。

# 使用md5sum计算文件的校验和,md5sum是一个长度为32个字符的十六进制串
[root@test ~]# md5sum test.txt
5be90d813d6c8856c467222c0a1e9f6c  test.txt
# 可以将输出的校验和重定向到一个文件中
[root@test ~]# md5sum test.txt > file_sum.md5
[root@test ~]# cat file_sum.md5
5be90d813d6c8856c467222c0a1e9f6c  test.txt
# md5sum -c 会输出校验和是否匹配的信息
[root@test ~]# md5sum -c *.md5
test.txt: OK
[root@test ~]# echo "888" >> test.txt
[root@test ~]# md5sum -c *.md5
test.txt: FAILED
md5sum: WARNING: 1 computed checksum did NOT match
[root@test ~]# echo $?
1
# 可以同时输出多个文件的校验和信息
[root@test ~]# md5sum test.txt sum.txt
69220795730f6030faeba1e065bda81d  test.txt
f3a4562cd2134c76b4ff170ce6f28fee  sum.txt
[root@test ~]# md5sum test.txt sum.txt >file_sum.md5
[root@test ~]# md5sum -c file_sum.md5
test.txt: OK
sum.txt: OK
[root@test ~]# echo $?
0
# 使用SHA-1校验,用法与MD5sum一致
[root@test ~]# sha1sum test.txt
10d74c5eb8e3c43c718a04f018d7bd349c0c5230  test.txt

对目录进行校验

校验和是从文件中计算得来的.对目录计算校验和意味着需要对目录中的所有文件以递归的方式进行计算。

[root@test ~]# md5deep  -rl test >directory.md5
# -r 使用递归遍历
# -l 使用相对路径。默认情况下,md5deep 会输出文件的绝对路径

[root@test ~]# cat directory.md5
d41d8cd98f00b204e9800998ecf8427e  test/a.pdf
d41d8cd98f00b204e9800998ecf8427e  test/dir1/dir2/linux/Centos7/1.txt
d41d8cd98f00b204e9800998ecf8427e  test/dir1/dir2/linux/2.txt
07e89644859e858ea880b9c716c33e76  test/txt.tar
# 进行核实
[root@test ~]# md5sum -c directory.md5
test/a.pdf: OK
test/dir1/dir2/linux/Centos7/1.txt: OK
test/dir1/dir2/linux/2.txt: OK
test/txt.tar: OK
# 结合find递归计算校验和
$ find directory_path -type f –print0 | xargs -0 md5sum >> directory.md5

2.7 加密工具与散列

加密技术主要用于防止数据遭遇未经授权的访问.和上面的校验和算法不同,加密算法可以无损地重构原始数据

crypt(需要安装)

crypt 命令通常并没有安装在 Linux 系统中。它是一个简单的加密工具,相对而言不是那么安全。该命令从 stdin 接受输入,要求用户创建口令,然后将加密数据输出到 stdout

$ crypt <input_file >output_file
Enter passphrase:
我们在命令行上提供口令:
$ crypt PASSPHRASE <input_file >encrypted_file
如果需要解密文件,可以使用:
$ crypt PASSPHRASE -d <encrypted_file >output_file

gpg

gpg (GNU privacy guard,GNU隐私保护)是一种应用广泛的工具,它使用加密技术来保护文件,以确保数据在送达目的地之前无法被读取

用 gpg 加密文件:
$ gpg -c filename
命令会采用交互方式读取口令并生成filename.gpg。使用以下命令解密gpg文件:
$ gpg filename.gpg
上述命令读取口令并解密文件

Base64

Base64 是一组相似的编码方案,它将二进制数据转换成以64为基数的形式(radix-64representation)

,以可读的 ASCII 字符串进行描述.这类编码程序可用于通过 E-mail 传输二进制数据。 base64 命令能够编码/解码 Base64 字符串。要将文件编码为 Base64格式 ,可以使用:

$ base64 filename > outputfile
或者
$ cat file | base64 > outputfile
base64 命令可以从 stdin 中读取。
解码Base64数据:
$ base64 -d file > outputfile
或者
$ cat base64_file | base64 -d > outputfile

2.8 排序与重复

sort排序

sort 命令能够对文本文件和 stdin 进行排序。它可以配合其他命令来生成所需要的输出。uniq 经常和 sort 一同使用,提取不重复(或重复)的行。

sort 用法

排序一组文件 :

$ sort file1.txt file2.txt > sorted.txt
或是
$ sort file1.txt file2.txt -o sorted.txt

按照数字排序 :

$ sort -n file.txt

按照逆序排序 :

$ sort -r file.txt

按照月份排序 :

$ sort -M file.txt

合并已排序过的文件 :

$ sort -m sorted1 sorted2

找出已排序文件中不重复的行 :

$ sort file1.txt file2.txt | uniq

检查文件是否已经排序过 :

#!/bin/bash
# 功能描述:排序
sort -C filename ;
if [ $? -eq 0 ]; then
echo Sorted;
else
echo Unsorted;
fi

按某一列排序 -k

[root@test ~]# cat data.txt
1 mac 2000
2 winxp 4000
3 bsd 1000
4 linux 1000

依照第二列进行排序

[root@test ~]# sort -k 2 data.txt
3 bsd 1000
4 linux 1000
1 mac 2000
2 winxp 4000

依照第一列,逆序形式排序

[root@test ~]# sort -nrk 1 data.txt
4 linux 1000
3 bsd 1000
2 winxp 4000
1 mac 2000

指定排序分隔符:

[root@test ~]# cat test.txt
first:10:a
second:6:b
third 3:11:c

通过冒号( : )进行分隔,按照第二列来进行排序

[root@test ~]# sort -t ":" -nk 2 test.txt
second:6:b
first:10:a
third 3:11:c

为了使 sort 的输出与以 \0 作为参数终止符的 xargs 命令相兼容,采用下面的命令:

$ sort -z data.txt | xargs -0
# 终止符\0使得xargs命令的使用更加安全

有时文本中可能会包含一些像空格之类的不必要的字符。如果需要忽略这些字符,并以字典序进行排序,可以使用:

$ sort -bd unsorted.txt

其中,选项 -b 用于忽略文件中的前导空白字符,选项 -d 用于指明以字典序进行排序。

uniq 用法

uniq 命令可以从给定输入中( stdin 或命令行参数指定的文件)找出唯一的行,报告或删除那些重复的行。 uniq只能作用于排过序的数据,因此 uniq 通常都与 sort 命令结合使用

删除重复的行:

[root@test ~]# sort file.txt|uniq

只显示不重复的行:

[root@test ~]# sort file.txt|uniq -u

只显示重复的行:

[root@test ~]# sort file.txt|uniq -d

统计各行在文件中出现的次数:

[root@test ~]# sort file.txt|uniq -c

忽略前 N 个字符,并指定用于比较的字符个数:

$ cat data.txt
u:01:gnu
d:04:linux
u:01:bash
u:01:hack
$ sort data.txt | uniq -s 2 -w 2
d:04:linux
u:01:bash

# -s 指定可以跳过前N个字符
# -w 指定用于比较的最大字符数

我们将命令输出作为 xargs 命令的输入的时候,最好为输出的各行添加一个 \0 值字节终止符。如果没有使用0 值字节终止符,那么在默认情况下,xargs 命令会用空格作为定界符分割参数。

uniq 命令生成包含 0 值字节终止符的输出:

$ uniq -z file.txt

如果某个文件名在文件中出现多次, uniq 命令只会将这个文件名写入 stdout 一次。

2.9 临时文件命名与随机数

shell 脚本经常需要存储临时数据。最适合存储临时数据的位置是 /tmp (该目录中的内容在系统重启后会被清空)。有两种方法可以为临时数据生成标准的文件名

mktemp 命令的用法非常简单。它生成一个具有唯一名称的文件并返回该文件名(如果创建
的是目录,则返回目录名)。
如果提供了定制模板, X 会被随机的字符(字母或数字)替换。注意, mktemp 正常工作的前
提是保证模板中至少要有 3X

创建临时文件:

[root@test ~]# filename=`mktemp`
[root@test ~]# echo $filename
/tmp/tmp.LrhnZX87sH

创建临时目录:

[root@test ~]# dirname=`mktemp -d`
[root@test ~]# echo $dirname
/tmp/tmp.JC2nQHzimy

生成文件名,不创建实际的文件或目录

[root@test ~]# tmpfile=`mktemp -u`
[root@test ~]# echo $tmpfile
/tmp/tmp.6LMEiY7LJT

基于模板创建临时文件名:

[root@test ~]#  mktemp test.XXX
test.vkD
[root@test ~]#  mktemp XXX.txt
H80.txt

基于模板创建临时文件名,不创建实际的文件或目录:

[root@test ~]#  mktemp -u XXX.txt
yno.txt

环境变量 **$RANDOM **总是返回一个随机数,用其作为临时文件名:

$ temp_file="/tmp/file-$RANDOM"

脚本中使用当前运行脚本的进程ID命名临时文件名:

$ temp_file="/tmp/var.$$"
# $$ 表示当前运行脚本的进程ID

2.10 分割文件和数据

2.10.1 split

split 命令可以用来分割文件。该命令接受文件名作为参数,然后创建出一系列体积更小的文件,其中依据字母序排在首位的那个文件对应于原始文件的第一部分,排在次序的文件对应于原始文件的第二部分,以此类推。

例如,通过指定分割大小,可以将100 KB 的文件分成一系列10 KB 的小文件。在 split 命令中,除了 kKB),我们还可以使用 MMB)、 GGB)、 c byte)和 wword)。

$ split -b 10k data.file
$ ls
data.file xaa xab xac xad xae xaf xag xah xai xaj

split 默认使用字母后缀。如果想使用数字后缀,需要使用 -d 选项。

此外, -a length 可以指定后缀长度:

[root@test ~]# split -b 10k data.file -d -a 4
[root@test ~]# ls
data.file  x0000  x0001  x0002  x0003  x0004  x0005  x0006  x0007  x0008  x0009

为分割后的文件制定能够文件名前缀:

[root@test ~]# split -b 10k data.file -d -a 4 split_file
[root@test ~]# ls
data.file	split_file0001	split_file0003  split_file0005  split_file0007	split_file0009	split_file0000  split_file0002  split_file0004  split_file0006	split_file0008

根据行数分割文件的话,可以使用 -l num_of_lines

$ split -l 10 data2.file

2.10.2 csplit

csplit 实用工具能够基于上下文来分隔文件。它依据的是行计数或正则表达式。

这个工具对于日志文件分割尤为有用。

# 测试数据
$ cat server.log
SERVER-1
[connection] 192.168.0.1 success
[connection] 192.168.0.2 failed
[disconnect] 192.168.0.3 pending
[connection] 192.168.0.4 success
SERVER-2
[connection] 192.168.0.1 failed
[connection] 192.168.0.2 failed
[disconnect] 192.168.0.3 success
[connection] 192.168.0.4 failed
SERVER-3
[connection] 192.168.0.1 pending
[connection] 192.168.0.2 pending
[disconnect] 192.168.0.3 pending
[connection] 192.168.0.4 failed

# 需求:将这个日志文件分割成server1.log、server2.log和server3.log,这些文件的内容分别取自原文件中不同的 SERVER 部分
# 实现
$ csplit server.log /SERVER/ -n 2 -s {*} -f server -b "%02d.log"
$ rm server00.log
$ ls
server01.log server02.log server03.log server.log
下面是这个命令的详细说明。
  /SERVER/ 用来匹配特定行,分割过程即从此处开始。
  /[REGEX]/ 用于描述文本模式。它从当前行(第一行)一直复制到(但不包括)包含 SERVER
的匹配行。
  {*} 表示根据匹配重复执行分割操作,直到文件末尾为止。可以用{ 整数}的形式来指定分
割执行的次数。
  -s 使命令进入静默模式,不打印其他信息。
  -n 指定分割后的文件名后缀的数字个数,例如01、02、03等。
  -f 指定分割后的文件名前缀(在上面的例子中,server就是前缀)。
  -b  指定后缀格式。例如 %02d.log ,类似于C语言中 printf 的参数格式。在这里:文件
名 = 前缀 + 后缀,也就是 server + %02d.log 。
因为分割后得到的第一个文件没有任何内容(匹配的单词就位于文件的第一行中),所以我
们删除了server00.log

2.11 根据扩展名切分文件名

我们可能会需要在保留扩展名的同时修改文件名、转换文件格式(保留文件名的同时修改扩展名)或提取部分文件名。

提取文件名

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

[root@test ~]# VAR=hack.fun.book.txt
[root@test ~]# echo ${VAR%.*}
hack.fun.book
[root@test ~]# echo ${VAR%%.*}
hack

提取扩展名:

借助 # 操作符可以提取出扩展名,这个操作符与 % 相似,不过求值方向是从左向右。

${VAR#*.}的含义是从 $VAR 中删除位于 # 右侧的通配符(即在前例中使用的 *. )所匹配的字符串。通配符从左向右进行匹配。

[root@test ~]# extension=${file_jpg#*.}
[root@test ~]# echo Extension is:$extension
[root@test ~]# Extension is:jpg

贪婪操作符 #### 从左向右进行贪婪匹配,并从指定变量中删除匹配结果

[root@test ~]# VAR=hack.fun.book.txt
[root@test ~]# echo ${VAR##*.}
txt

2.12 批量重命名和移动

综合运用 findrename mv,我们其实可以完成很多操作。

用特定的格式重命名当前目录下的图像文件:

#!/bin/bash
# 文件名 rename.sh
# 用途: 重命名 .jpg 和 .png 文件

count=1;
for img in *.jpg *.png
do
new=image-$count.${img##*.}
mv "$img" "$new" 2> /dev/null
if [ $? -eq 0 ];
then
echo "Renaming $img to $new"
let count++
fi
done

忽略大小写用 .[jJ][pP][gG] 或者用 find 命令的 iname 参数:

#!/bin/bash

count=1
# 选项-o用于指定多个-iname
for img in `find . -maxdepth 1 -iname "*.jpg" -o -iname  "*.png" -type f `
do
  # ${img##*.}获取扩展名
        new=image-$count.${img##*.}
        mv $img $new > /dev/null
        if [ $? -eq 0 ];
		then
		echo "Renaming $img to $new"
		let count++
		fi
done

将所有的 .mp3 文件移入给定的目录,可以使用:

$ find path -type f -name "*.mp3" -exec mv {} target_dir \;

rename

mv 命令只能对单个文件重命名,ename 命令可以批量文件重命名。

C 语言版 rename 的语法格式是:

rename from to file

这个命令有三个参数,分别是 from : 修改什么名字,to :改成什么名字,file 需要修改的文件是哪些。

# 将.png换成.PNG
[root@test rh]# rename .png .PNG *.png

Perl 版的 rename ,可以使用正则表达式:

# 将 *.JPG更名为 *.jpg
$ rename *.JPG *.jpg

# 将文件名中的空格替换成字符“_”
$ rename 's/ /_/g' *

# 转换文件名的大小写
$ rename 'y/A-Z/a-z/' *

2.13 交互输入自动化

示例脚本:

#!/bin/bash
# 文件名: interactive.sh
read -p "Enter number:" no ;
read -p "Enter name:" name
echo You have entered $no, $name;

按照下面的方法向命令自动发送输入:

$ echo -e "1\nhello\n" | ./interactive.sh** **You have entered 1, hello**

通过键盘输入的字符,将其转换为文本 "1\nhello\n" 发送给 stdin ,这样和手动输入效果相同。

也可放进文件然后重定向到脚本:

$ ./interactive.sh < input.data

expect

except 是一个和 shell 类似的解释器,默认没有这个命令,需要安装下。

expect 有3个主要命令:

image

expect 等特定的输入提示,通过检查输入提示来发送数据。

#!/usr/bin/expect
# 文件名: automate_expect.sh
spawn ./interactive .sh
expect "Enter number:"
send "1\n"
expect "Enter name:"
send "hello\n"
expect eof

也可以使用下面这种形式:

#!/usr/bin/expect
spawn ./interactive.sh
expect {
     "Enter number:" {
         send "102\n"
         exp_continue
      }
      "Enter name:" {
      send "hello\n"
        exp_continue
      }
}

expect 匹配的字符可以使用 * 通配

设置超时时间,单位是秒,timeout -1 为永不超时:

set timeout -1

这个超时时间是就是 expect 等待接收进程发出的字符串的时间,超过这个时间没匹配到就不再等待。

第3章 以文件之名

3.1 生成任意大小的文件

利用 dd 命令创建特定大小的文件。

dd 命令会克隆给定的输入内容,然后将一模一样的一份副本写入到输出 stdin ,设备文件,普通文件等都可作为输入。stdout ,设备文件,普通文件等也可作为输出。

创建一个内容全部为零的 1MB 大小的文件:

[root@test ~]# dd if=/dev/zero of=chuanhe.data bs=1M count=1
1+0 records in
1+0 records out
1048576 bytes (1.0 MB) copied, 0.42497 s, 2.5 MB/s

/dev/zero 是一个特殊的字符设备,它会返回 0 值字节( \0 )。

  • if 表示输入文件(input file);

  • of 表示输出文件(output file);

  • bs 指定了以字节为单位的块大小(block size);

  • count 表示需要被复制的块数;

如果不指定输入参数(if), dd 会从 stdin 中读取输入。如果不指定输出参数( of ),则 dd 会使用 stdout 作为输出。

块大小可以使用各种计量单位:

image

3.2 文本文件的交集与差集

comm 命令可用于两个文件之间的比较。它有很多不错的选项可用来调整输出,以便我们执行交集、求差(difference)以及差集操作。

  • 交集:打印出两个文件所共有的行。A ∩ B
  • 求差:打印出指定文件所包含的且不相同的那些行。Cu (A ∩ B)
  • 差集:打印出包含在文件A中,但不包含在其他指定文件中的那些行。A ∩ CuB
$ cat A.txt
apple
orange
gold
silver
steel
iron

$ cat B.txt
orange
gold
cookies
carrot

$ sort A.txt -o A.txt ; sort B.txt -o B.txt
$ comm A.txt B.txt
apple
        carrot
        cookies

                gold

iron
                orange
silver
steel

comm A.txt B.txt输出结果有3列:第一列是 A ∩ CuB,第二列是 B ∩ CuA,第三列是 A ∩ B

有一些选项可以按照我们的需求进行格式化输出,例如:

  • -1 从输出中删除第一列;
  • -2 从输出中删除第二列;
  • -3 从输出中删除第三列。

打印两个文件的交集 ( A ∩ B ):

$ comm A.txt B.txt -1 -2
gold
orange

打印出两个文件中不相同的行(Cu ( A ∩ B )):

$ comm A.txt B.txt -3
apple

        carrot
        cookies

iron
silver
steel

两列合成一列:

$ comm A.txt B.txt -3 | sed 's/^\t//'
apple
carrot
cookies
iron
silver
steel

A.txt 的差集 ( A ∩ CuB )

$ comm A.txt B.txt -2 -3

B.txt 的差集( B ∩ CuA )

$ comm A.txt B.txt -1 -3

3.3 查找并删除重复的文件

以下脚本可查找并删除重复文件:

#!/bin/bash
# 删除重复的文件
ls -lS --time-style=long-iso | awk 'BEGIN{
 getline;getline;
 name1=$8;size1=$5      # BEGIN里面相当于一些初始化操作
}
{ # 循环语句块,读取每一行进行比较。
 name2=$8;
 if (size1==$5)
        {
                "md5sum "name1 | getline;csum=$1;
                "md5sum "name2 | getline;csum=$1;
                if (csum1==csum2)
                {
                        print name1;print name2;
                }
        };
 name1=name2;size1=$5;
}'| sort -u > duplicate_files   # 这里这么多其实目的是将md5相同的文件名在每行放挨着,方便后面去重

cat duplicate_files | xargs -I {} md5sum {} | \
sort | uniq -w 32 | awk '{print $2}' | sort -u >unique_files  #这个是去重后要保留的文件名

echo "Removing"
comm duplicate_files unique_files -2 -3 | tee /root/rh/err.txt | xargs rm  #删除不保留的
echo Removed duplicates files successfully.

sort -u file 等价于 sort file | uniq ,即排序后删除重复行。

awk 中,外部命令的输出可以用下面的方法读取:

"cmd" | getline

对当前目录下的所有文件按照文件大小进行排序,并列出文件的详细信息:

ls -lS --time-style=long-iso

# --time-style=long-iso显示日期和时间(包括年),以长格式显示yyyy-mm-dd hh:mm:ss

其实没必要这么麻烦,下面这样删除也可以:

md5sum * |sort -k 1|awk '{print $2}'|sort -u > /tmp/duplicate_files

md5sum * |sort -k 1|uniq -w 32|awk '{print $2}'|sort -u >/tmp/unique_files

comm /tmp/duplicate_files /tmp/unique_files -2 -3|xargs rm

3.4 创建目录

mkdir 命令用于创建目录:

$ mkdir dirpath

如果目录已经存在,会返回“ File exists ”错误信息:

mkdir: cannot create directory `dir_name': File exists

下面的代码可找出路径中的每个目录是否存在:

if  [ -e /home/slynux ]; then   # -e是一个用在条件判断 [ ] 中的参数,可用以判断某个文件是否存在。
  # 创建下一级目录
fi

创建多级目录树:

$ mkdir -p  /a/b/c

3.5 文件权限和粘滞位

用命令 ls -l 可以列出文件的权限

[root@test ~]# ls -l A.txt
-rw-r--r--. 1 root root 36 Mar  3 13:26 A.txt

第1列表明了文件类型。

  • “-” —— 普通文件。
  • “d” —— 目录。
  • “c” —— 字符设备。
  • “b” —— 块设备。
  • “l” —— 符号链接。
  • “s” —— 套接字。
  • “p” —— 管道。

设置文件权限:

$ chmod u=rwx g=rw o=r filename
  • u = 指定用户权限
  • g = 指定用户组权限
  • o = 指定其他实体权限

增加可执行权限:

$ chmod o+x filename

给所有权限类别增加可执行权限:

$ chmod a+x filename

删除权限,则使用 -

$ chmod a-x filename

用八进制数来设置权限:

  • r-- = 4
  • -w- = 2
  • --x = 1

rwx rw- r-- 等于764,那么:

$ chmod 764 filename

以递归的方式修改当前目录下的所有文件和子目录的权限:

$ chmod 777 . -R

要更改文件的所有权:

$ chown user:group filename

以递归的方式设置所有权:

$ chown user:group . -R

粘滞位:只有目录的所有者才能够删除目录中的文件,即使用户组和其他用户拥有足够的权限也不能执行该删除操作。

粘滞位出现在其他用户权限中的执行权限( x )位置。它使用 tT 来表示。如果没有设置执行权限,但设置了粘滞位,那么是 T ;如果同时设置了执行权限和粘滞位,就使用 t

设置粘滞位:

$ chmod a+t directory_name

以不同的用户运行可执行文件:

有一个叫做 setuid 的特殊文件权限,它允许其他用户以文件所有者的身份来执行文件。

$ chmod +s executable_file
# chown root.root executable_file
# chmod +s executable_file
$ ./executable_file     # 现在,这个文件实际上每次都是以超级用户的身份来执行。

创建不可修改文件:

chattr +i file

此时文件已不可修改:

rm file
rm: cannot remove `file': Operation not permitted

移除不可修改属性:

chattr -i file

3.6 批量生成空白文件

创建一个空白文件:

$ touch filename

批量生成名字不同的空白文件:

for name in `seq  -f '%g.txt' 10`
do
touch $name
done

for name in {1..10}.txt
do
touch $name
done

{1..10} 会扩展成一个字符串“ 1, 2, 3,...,10 "。还可以使用其他简写样式,比如 test{a..z}.txt 等。

如果文件已经存在,那么 touch 命令将会与该文件相关的所有时间戳更改为当前时间。

如果我们只想更改某些时间戳,则可以使用下面的选项。

  • touch -a 只更改文件访问时间(access time)。
  • touch -m 只更改文件内容修改时间(modification time)。

将时间戳更改为指定时间:

touch -d "2023-03-6 16:27" 10.txt

3.7 查找符号链接及其指向目标

创建符号链接:

ln -s /root/rh test111

打印当前目录下的符号链接:

ls -l |grep "^l"    # ^ 是字符串起始标记

打印出当前目录及其子目录下的符号链接:

find . -type l -print

打印出符号链接所指向的目标路径:

readlink test

3.8 枚举文件类型统计信息

① 打印文件类型信息

[root@test ~]# file /etc/passwd
/etc/passwd: ASCII text

② 打印不包括文件名在内的文件信息

[root@test ~]# file -b /etc/passwd
ASCII text

生成文件统计信息的脚本:

#!/bin/bash
if [ $# -ne 1 ];
then
        echo "Useage:$0 is not filepath"
        exit
fi

filepath=$1
declare -A sateArray;

while read line;
do
        filetype=`file -b "$line" | cut -d, -f1`
        let sateArray["$filetype"]++;
       
done < <(find $filepath -type f -print)
echo =================== File Type and counts ================
# 注意遍历关联数组要加双引号,要不然会以空格分隔进行遍历
for ftype in "${!sateArray[@]}";
do
        echo $ftype : ${sateArray[$ftype]}
done

< <(find $filepath -type f -print) 注意,第一个 < 用于输入重定向,第二个 < 用于将子进程的输出转换成相应的 filename(文件名)(注:这里使用了进程替换)。这两个 < 之间有一个空格,因此shell 并不会将其解释为 << 操作符。

# while循环的形式如下
while read line;
do something
done < filename
# <(find $path -type f -print) 等同于上述模块的filename

3.9 只列出目录的各种方法

ls -d */

② 使用 grep 结合 ls -F
原理:当使用 ls -F 选项时,所有的输出项后面都会多出一个代表文件类型的字符,如 @,*,| 等,目录对应的是 / 字符。使用 grep 过滤出以 / 结尾的输出项。

ls -F|grep "/$"

ls -l |grep "^d"

④ 使用 find

find  . -type d -maxdepth 1 -print

3.10 pushd和popd

pushd popd 可以用于在多个目录之间切换。这两个命令会创建一个路径栈(后进先出)。

① 压入并切换路径

[root@test usr]# pushd /home/
/home/rh /usr

② 再压入一个目录

[root@test rh]# pushd /usr/src/
/usr/src /home/ /usr

③ 查看栈的内容

[root@test src]# dirs
/usr/src /home/ /usr

④ 切换到栈中任意一个路径时,将每条路径从 0 编号到 n ,然后使用你希望切换到的路径编号

[root@test usr]# dirs
/usr /usr/src /home/
0       1            2
# 假设想切到/home/rh,注意栈中目录的顺序会变
[root@test usr]# pushd +2
/home/ /usr /usr/src

⑤ 删除最近压入的路径并切换到下一个目录

[root@test rh]# popd 
/usr /usr/src

⑥ 用 popd +num 可以从栈中移除特定的路径。 num 是从左到右、从 0n 开始计数的

[root@test usr]# dirs
/usr /usr/src /home/
0       1            2
# 删除 /usr
[root@test usr]# popd +0
/usr/src /home/

⑦ 假设访问 /usr之前,所在目录为,/home/。当跳转到 /usr 时,若想再访问上一次访问的目录 /home/可以使用:

[root@test usr]# cd -
/home/rh

3.11 打印目录树

tree 命令能够以图形化的树状结构打印文件和目录。

-P 选项可以只显示出匹配指定模式的文件

[root@ceshi ~]# tree . -P '*.sh'
.
├── a
│   └── test.sh
├── b
└── c

3 directories, 1 file

-I 选项可以只显示出不匹配指定模式的文件

tree path -I PATTERN

-h 选项可以同时打印出文件和目录的大小

tree -h

④ 生成 HTML 形式的目录树文件

[root@ceshi ~]# tree ./ -Hh http://localhost -o out.html

3.12 head 与 tail

① 打印前 10

head file

② 从 stdin 读取数据:

$ cat text | head

③ 指定打印前几行

head -n 5 file

④ 打印除了最后 M 行之外所有的行

$ head -n -M file

# 注意,-M表示一个负数。
[root@ceshi ~]# seq 11 | head -n -5
1
2
3
4
5
6

⑤ 打印文件的最后 10

tail file

⑥ 从 stdin 中读取输入

cat text | tail

⑦ 打印最后 5 行:

tail -n 5 file

⑧ 打印除了前 M 行之外所有的行

tail -n +(M+1)

例如,打印除前 5 行之外的所有行, M+1=6 ,因此使用下列命令

seq 100 | tail -n +6

⑨ 实时监听所更新的文本

tail -f growing_file

3.13 wc

wc 是一个用于统计行、单词和字符数量的实用工具。它是 Word Count(单词计数)的缩写

① 统计行数

[root@ceshi ~]# wc -l test.txt 
6 test.txt

② 统计单词数

[root@ceshi ~]# wc -w test.txt 
5 test.txt

cat test.txt | wc -w

③ 统计字符数

[root@ceshi ~]# cat test.txt
my name is river
[root@ceshi ~]# wc -c test.txt 
17 test.txt
[root@ceshi ~]# cat test.txt | wc -c
17

以上统计结果似乎多了一个字符,我们显示所有字符发现,实际上是因为行尾有个$:

vim test.txt
my name is river$
~
~
:set list                                            1,1           All

我们可以按照下面的方法统计文本中的字符数:

echo -n 1234 | wc -c
4
# -n 用于避免 echo 添加额外的换行符

④ 不使用任何选项时,wc 会打印出行,单词和字符的数量

[root@ceshi ~]# wc test.txt 
 6  5 17 test.txt

⑤ 使用 -L 选项打印出文件中最长一行的长度

[root@ceshi ~]# wc test.txt -L
3 test.txt
posted @ 2023-03-09 10:26  可惜流年  阅读(50)  评论(0编辑  收藏  举报