read命令
read命令的一般形式为:
read variables
该命令执行时,Shell会从标准输入中读取一行,然后将第一个单词分配给variables中列出的第一个变量,第二个单词分配给第二个变量,以此类推。如果行中的单词多于列表中的变量,那么多出的单词全部分配给最后一个变量。例如,下列命令:
read x y
会从标准输入中读入一行,将第一个单词分配给变量x,将行中余下的内容分配给变量y。按照这种处理逻辑,下列命令:
read text
会读取并将一整行保存到Shell变量text中。
文件复制程序
下面来编写一个cp命令的简化版本,借此实践一下read命令。我们把这个程序叫做mycp,它可以接收两个参数:源文件和目标文件。如果目标文件已经存在,程序会发出警告并询问是否需要继续进行复制。如果回答是”yes”,则继续执行复制操作;否则,退出程序。
## 复制文件#if [ "$#" -ne 2 ] ; thenecho "Usage: mycp from to"exit 1fifrom="$1"to="$2"## 检查目标文件是否已经存在#if [ -e "$to" ] ; thenecho "$to already exists; overwrite (yes/no)?"read answerif [ "$answer" != yes ] ; thenecho "Copy not performed"exit 0fifi## 如果目标文件不存在或用户输入了 yes#cp $from $to # 执行复制操作
特殊的echo转义字符
mycp命令有一个稍有点烦人的地方:echo命令运行过之后,由用户输入的响应出现在另一行上。这是因为echo命令会自动在最后一个参数后面加上一个用于终止的换行符。好在有办法阻止这种行为:只需要在echo命令的最后加上特殊的转义字符\c就可以了。这告诉echo 在显示完最后一个参数之后不输出换行符。如果你将mycp中的echo命令改成下面这样:
echo "$to already exists; overwrite (yes/no)? \c"
这样用户输入就会出现在提示信息的右侧了。记住,\c是由echo解释的,而非Shell,这意味着必须将其引用起来交给echo。
| 字符 | 输出 |
|---|---|
| \b | 退格 |
| \c | 忽略输出中最后的换行符 |
| \f | 换页 |
| \n | 回车换行 |
| \r | 回车 |
| \t | 制表符 |
| \\ | 反斜线 |
| \0nnn | ASCII 值为 nnn 的字符,其中 nnn 是 1~3 位的八进制数 |
read的退出状态
除非是碰到了文件结尾,read都会返回为0的退出状态码。如果是从终端读取数据,这意味着用户键入了Ctrl+d。如果是从文件读取数据,这意味着文件中已经没有数据可读了。很容易写出一个从文件或终端中读取数据行的循环。接下来名为addi的程序会读入包含了一对数字的行,这对数字会被相加并将之和写入标准输出:
## 将标准输入中的一对整数相加#while read n1 n2doecho $(( $n1 + $n2))done
basename命令
basename命令可以剥离参数的所有目录部分,得到其基础文件名,例如,basename /usr/bin/troff可以得到troff。basename troff也可以得到 troff。
变量$$
Shell会将替换成登录Shell的进程ID号。因为系统中每个进程都拥有唯一的进程ID,在文件名中使用就避免了不同的进程使用相同文件名的可能。
printf命令
对于简单的信息而言,echo已经足够了,但有时候你可能想输出格式化信息,如按列对齐的数据。printf命令的一般形式为:
printf "format" arg1 arg2 ...
其中format是一个字符串,用于详细描述后续数值的显示格式。因为格式化字符串要作为单个参数,而且有可能其中会包含特殊字符和空格,最好是将其放入引号中。格式化字符串中那些前面没有百分号(%)的字符会被直接写入标准输出。按照最简单的用法,printf的效果和echo差不多(只要你记得每行末尾加上\n作为换行符,如下所示):
$ printf "Hello world!\n"Hello world!
前面有百分号的字符叫做格式规范,用于告知printf如何显示对应的参数。格式化字符串中的每个百分号都应该有一个对应的参数,除了特殊规范%%,它会显示出一个百分号。来看一个printf的简单例子:
$ printf "This is a number: %d\n" 10This is a number: 10
不同的格式规范字符。
| 字符 | 功能 |
|---|---|
| %d | 整数 |
| %u | 无符号整数 |
| %o | 八进制整数 |
| %x | 十六进制整数,使用 a~f |
| %X | 十六进制整数,使用 A~F |
| %c | 单个字符 |
| %s | 字符串字面量 |
| %b | 包含转义字符的字符串 |
| %% | 百分号 |
转换规范的一般格式为: sh %[flags][width][.precision]type type: 是上图中的转换规范字符。 flags: 有效的flags是-、+、#和空格。 - -: 将输出的数值左对齐。 - +: 使得printf 在整数前面加上+或-(默认只有负数才输出符号)。 - #: 使得printf在八进制数前加上0,在十六进制数前加上0x或0X(分别使用%#x或%#X来指定)。 - 空格: 使得printf在正数前面加上一个空格,在负数前面加上-,以起到对齐的作用。 width: 是一个整数,用来指定输出参数时的最小字段宽度。对应的参数采用右对齐的形式,除非flags使用了-。 .precision: 是一个正数,指定了%d、%u、%o、%x 及%X所显示出的最小数位个数。在值的左侧,会使用0进行填充。对于字符串,修饰符.precision指定了所要显示出的字符串的最大字符数。如果字符串的长度大于precision,会在右侧被截断。
如果将width或precision中的数字替换成*,那么待显示的值之前的参数必须是一个数字,该数字分别用作宽度或精度。如果两个位置上(width和precision)用的全都是*,那么待显示的值之前必须有两个整数参数,作为宽度和精度:
$ printf "%*s%*.*s\n" 12 "test one" 10 2 "test two"test one te$ printf "%12s%10.2s\n" "test one" "test two"test one te
可以看出,例子中两个 printf 的结果是一样的。在第一个printf中,12用作第一个字符串的宽度,10和2分别用作第二个字符串的宽度和精度。在第二个printf中,这些数字是作为格式化规范的一部分来指定的。
## 对齐两列数字# (最多支持包括符号在内的 12 位数)cat $* |while read number1 number2doprintf "%12d %12d\n" $number1 $number2done
$ cat data1234 7960593 -595395 3043234 999-394 -493$ align data1234 7960593 -595395 3043234 999-394 -493
