4

Shell 标准输入和输出

 1 year ago
source link: https://blog.51cto.com/boxuegu/5939859
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Shell 标准输入和输出

精选 原创

博学谷狂野架构师 2022-12-15 14:45:53 博主文章分类:技术干货 ©著作权

文章标签 后端 java 文章分类 Java 编程语言 阅读数156

无论是要交给程序处理的数据,还是控制脚本的简单命令,都少不了输入和输出。程序要做的第一件事就是处理如同一阴一阳的“输入与输出”。

1 、从文件获取输入

当我们希望向文件输出内容时,我们可以通过符号 > 或 >> 实现。而用代表输入重定向的符号 < 可以从文件中读取数据,如下:

$ wc < my.file

之所以选择这种形状的操作符号,原因在于它们可以从视觉上提示重定向的方向。

很多 shell 命令可以接受一个或多个文件名作为参数,但如果没有给出文件名,命令就会从标准输入读取。使用这种命令时,可以采用command filename 或者 command < filename,这两种形式的结果没什么区别。在这个例子中,wc 是这样,换作 cat 或其他命令,也是如此。

2、将数据与脚本存放在一起

< 可以从文件读取数据,当你需要获得脚本输入,但又不想用单独的文件时,使用 <<(here-document)从命令行而非文件重定向输入文本。如果放在 shell 脚本中,则脚本文件可以同时包含数据与代码。

以下是名为 ext.sh 的 shell 脚本示例:

# 下面是here-document
grep $1 <<EOF

mike x.123

joe x.234

sue x.555

pete x.818

sara x.822

bill x.919

EOF

当我们运行此脚本,可以传入一个参数,如下调用:

$ ./ext.sh bill
# 输出以下内容
bill x.919

grep 命令查找第一个参数是否在指定文件中出现,如果没有指定文件,那么它会在标准输入中查找。通过设置 here document,告诉 shell 将标准输入重定向(临时)到此处。<< 语法表示我们想创建一个临时输入源,EOF 是一个任意的字符串(你想用什么都行),用作临时输入的终止符。它并不属于输入的一部分,只是作为标记告诉输入在哪里结束。

3、避免here-document中的怪异行为

here-document 在使用时可能会出现一些怪异的行为。你想用上一节介绍的方法来保存一份简单的捐赠人列表,因此创建了一个名为donors.sh 的文件,如下所示:

# 简单地查找慷慨的捐赠人

grep $1 <<EOF

pete $100

joe $200

sam $ 25

bill $ 9

EOF

但是运行时出现了奇怪的输出:

$ ./donors.sh bill

pete bill00

bill $ 9

$ ./donors.sh pete

pete pete00

正常情况下(除非使用了转义语法),bash 手册页中是这样说的:“……here-document 的每一行都要执行参数扩展、命令替换以及算术扩展”。因此,最初的 donors 脚本中所发生的事情是捐赠额被当作 shell 变量了。例如,$100 被视为 shell 变量 $1,随后跟着两个 0。这就是为什么我们在搜索“pete”时,得到的是 pete00;搜索“bill”时,得到的是 bill00。

解决办法:

通过转义结尾标记中的任意或所有字符,修改脚本内容,关闭 here-document 内部的 shell 特性(注意观察EOF位置的变化):

# 简单地查找慷慨的捐赠人
grep $1 <<'EOF'

pete $100

joe $200

sam $ 25

bill $ 9

EOF

尽管其中存在非常微妙的区别,但也可以将 <<EOF 替换成 <<\EOF或 <<‘EOF’,甚至是 <<E\OF,都没问题。尽管这并不是最优雅的语法,但足以告诉 bash 你希望区别处理 here-document 中的内容。如果我们转义了 EOF 的部分或全部字符,那么 bash 就知道不用执行扩展,这样就符合我们的预期行为了。

$ ./donors.sh pete

pete $100

4、获取用户输入

输入不止从文件中获取,有时我们还需要获取用户输入的内容。此时,我们需要用到read命令,如下:

$ read
$ read -p "answer me this " ANSWER

不带参数的 read 语句会读取用户输入并将其保存在 shell 变量REPLY 中,这是 read 的最简形式。如果希望 bash 在读取用户输入前先输出提示信息,可以使用 -p 选项。-p 之后的单词就是提示信息,如果想提供多个单词,可以将其引用起来。记住,要在提示信息结尾处加上标点符号或空格,因为光标会停在那里等待输入。-t 选项可以设置超时值。指定秒数达到后,不管用户是否输入,read 语句都会返回。我们的示例同时用到了 -t 和 -p 选项,但你也可以单独使用 -t 选项。

上面的方式获取用户输入时会以明文回显,那适用密码输入么?

当我们需要用户输入敏感信息时,需要禁止用户输入内容的回显。此时用 read 命令读取用户输入,需要加上一个特殊选项来关闭回显:

read -s -p "password: " PASSWD

printf "%b" "\n"

-s 选项告诉 read 命令不要回显输入的字符(s 代表 silent),-p 选项指明下一个参数是提示信息,会在读取用户输入之前显示。从用户那里读取到的输入行保存在变量 $PASSWD 中。在 read 之后,我们用 printf 输出了一个换行符。这里的printf 不能少,因为 read -s 会关闭字符回显。如果禁止了回显功能,当用户按下回车键时,就不会回显换行符,后续输出就会和提示信息出现在同一行。输出换行符会将光标带到下一行。

当然,我们也可以选择一行,如下:

read -s -p "password: " PASSWD ; printf "%b" "\n"

Shell标准输出

如果无法产生输出,那么软件也就没什么价值了,但长久以来,I/O一直是难缠的计算领域之一。问题是有太多类型的输出,向屏幕写入不同于向文件写入,向文件写入也不同于向磁带或闪存写入。所以,对于输出会产生一些问题,如下:

  • 软件开发人员是否要针对各种输出设备编写代码,甚至包括尚未发明的设备?
  • 写到哪个文件?程序怎么知道是该写入代表终端窗口的文件、磁盘文件还是其他种类的文件?

显然,如果把这些事情都交给每个程序员是不合理的,所以这种事情留给shell 就行了。

1、输出到终端/终端窗口

想要用 shell 命令产生一些简单的输出,使用内建命令 echo。命令行中的所有参数都会打印到屏幕上。

echo Please wait.
Please wait.

结果和在 bash 提示符(字符 $)后输入该命令相同:

Shell 标准输入和输出_后端

echo 是最简单的 bash 命令之一。该命令可以将参数输出到屏幕上。但是有几点需要记住:

  • 首先,shell 负责解析 echo 的命令行参数。将参数交给 echo前,shell 会完成所有的替换、通配符匹配等操作。
  • 其次,在解析参数时,参数之间的空白字符会被忽略,如下图:

Shell 标准输入和输出_java_02

shell 对参数间的空白字符没有太多限制,这通常是一种不错的特性。但对于 echo 来说,就有点烦人了。

  • 保留输出中的空白字符。将字符放入引号中就可以保留空白字符,如下图:

Shell 标准输入和输出_java_03

引号中的单词组成了 echo 命令的单个参数。该参数是一个字符串,shell 不会干涉字符串的内容。实际上可以用单引号(‘’)明确告诉shell 不要干涉字符串。

2、在输出中加入更多格式控制

使用内建命令 printf。例如:

printf '%s = %d\n' Lines $LINES
Lines = 24
$ printf '%-10.10s = %4.2f\n' 'Gigahertz' 1.92735

Gigahertz = 1.93

内建命令 printf 的行为和 C 语言中的同名库函数相似,其中第一个参数是格式控制字符串,之后的参数都根据格式规范(%)进行格式化。

% 和格式类型(本例为 s 或 f)之间的数字提供了额外的格式化细节。

对于浮点类型(f),第一个数字(指示符 4.2 中的 4)是整个字段的宽度。第二个数字(2)是应该在小数点右侧打印出的数位量。注意,结果会按照四舍五入处理。

对于字符串,第一个数字是字段的最大宽度,第二个数字是要输出的字符数量。根据需要,字符串会被截断(长于 max)或用空白填充(不足 min)。如果指示符 maxmin 相同,那么就可以确保字符串按照该长度输出。指示符左侧的负号表示字符串向左对齐(在字段宽度内)。如果不使用负号,则字符串向右对齐

3、消除输出中的换行符

希望输出中不包含 echo 默认生成的换行符。使用 printf,做法很简单,去掉格式化字符串末尾的 \n 即可,如下图:

printf "%s %s" next prompt
Shell 标准输入和输出_shell_04

如果是 echo,则使用 -n 选项:

$ echo -n prompt
Shell 标准输入和输出_java_05

因为 printf 的格式字符串(第一个参数)末尾并没有换行符,所以命令行提示符($)出现在了 printf 的输出之后。该特性在shell 脚本中用处更大,你可能希望在形成一整行前由多条语句逐部分输出,或者在读取输入前显示用户提示。

换作 echo 命令(参见 15.6 节),消除换行符的方法有两种。

首先,-n 选项能够抑制输出行尾的换行符。

另外,echo 命令还可以处理多种具有特殊含义的转义序列(如表示换行符的 \n),这些转移序列与 C 语言字符串中的类似。调用 echo 命令时加上 -e 选项。其中一种转义序列是 \C,它并不会输出什么字符,而是禁止在行尾输出换行符。如下图:

$ echo -e 'hi\c'
Shell 标准输入和输出_shell_06

4、保存命令输出

如过想把命令输出保存在文件中,用 > 符号告诉 shell 将输出重定向至文件,例如:

$ echo fill it up

fill it up

$ echo fill it up > file.txt

我们来查看一下文件 file.txt 的内容,看看其中是否包含了命令的输出:

$ cat file.txt

fill it up

示例第一部分的第一行中出现的 echo 命令包含了 3 个要输出的参数。第二行用 > 将这些输出保存到文件 file.txt 中,这就是看不

到 echo 输出的原因。

示例第二部分用 cat 命令显示文件内容。我们可以看出,文件中包含的正是 echo 本该输出的内容。

cat 命令得名自一个较长的单词 concatenation(拼接)。该命令会将出现在命令行上的文件的输出拼接在一起,如果你输入 cat

file1 file2 file3,那么这些文件的内容会逐个发送到终端窗口。如果一个大文件被分成了两半,你也可以用 cat 将其恢复原样(也就是将两部分拼接起来),这只需将输出保存到另一个文件中:

cat first.half second.half > mergeFile.txt

5、将输出保存到其他文件

如想要用重定向将输出保存到当前目录之外的其他位置,重定向输出时加上路径,如下:

echo some more data > /tmp/echo.out
echo some more data > ../../over.here

出现在重定向符号(>)后的文件名其实就是路径名。如果没有任何限定部分,那么文件就会放置在当前目录中。

如果文件名以斜线(/)起始是绝对路径名,此时文件会被放置在文件系统层次结构(目录树)中以根目录起始的指定位置。

第二个例子中,我们使用了相对路径名 …/…/over.here,其中的… 是一个指向父目录的特殊目录,存在于每个目录中。

6、将输出和错误消息发送到不同文件

希望获得程序的输出,但不想输出被出现的错误消息弄乱。要保存的错误消息混杂在程序输出中不容易找出,可将输出和错误消息重定向到不同文件,如下:

$ myprogram 1> messages.out 2> message.err

或者采用更常见的方法:

$ myprogram > messages.out 2> message.err

shell 会创建两个输出文件。

第一个是messages.out,程序 myprogram 的所有输出都会重定向到该文件。

第二个是message.err,程序myprogram 的所有错误消息都会重定向到 message.err。

在 1> 和 2> 中,数字表示文件 描述符。

  • 1 代表标准输出(STDOUT),
  • 2 代表标准错误(STDERR)。
  • 0 代表标准输入(STDIN)。

如果不指定数字,则假定为 STDOUT。

7、将输出和错误消息发送到同一文件

利用重定向,我们可以将输出或错误消息保存到单独的文件中,但如何将两者送往同一文件呢?用 shell 语法将标准错误消息重定向到和标准输出相同的地方。

$ myprogram >& outfile
$ myprogram &> outfile

又或者老式且略烦琐的写法:

$ myprogram > outfile 2>&1

其中,myprogram是准备向 STDERR 和 STDOUT 生成输出的程序。

&> 和 >& 只是将 STDOUT 和 STDERR 发送到相同地方的便捷写法。

8、追加输出

每次重定向输出,都会产生一个全新的输出文件。如果想要两次(或三次、四次……)重定向输出,同时又不想破坏之前的输出,该怎么办呢?

在 bash 的重定向符号中,双大于号(>>)表示追加输出:

$ ls > /tmp/ls.out
$ cd ../elsewhere
$ ls >> /tmp/ls.out
$ cd ../anotherdir
$ ls >> /tmp/ls.out

如果存在同名文件,第一行中的重定向会将其截断,并将 ls 命令的输出保存在这个已被清空的文件中。

后两次调用 ls 时使用了双大于号(>>),表示向输出文件中追加内容,而不是覆盖其原有内容。

如果想要同时重定向错误消息(STDERR),可以将 STDERR 的重定向放在后面,如下所示:

ls >> /tmp/ls.out 2>&1

在 bash 4 中,你可以将这两个重定向合二为一:

$ ls &>> /tmp/ls.out

该命令会重定向 STDERR 和 STDOUT,并将两者追加到指定文件中。& 符号必须先出现,且这 3 个字符之间不能有空格

9、丢弃输出

你有时不想将输出保存到文件中或者有时甚至不想看到输出。如我们在查找某个文件时,忽略那些没有权限的提示,如下图:

Shell 标准输入和输出_java_07

此时,我们可以将输出重定向到 /dev/null,如下所示:

$ find / -name myfile 2> /dev/null

其实,你可以将不想要的输出重定向到文件,然后再将其删除。但还有一个更简单的方法。Unix 和 Linux 系统都存在一个特殊设备,该设备并非真实的硬件,而仅仅是一个位桶(bit bucket),我们可以将不需要的数据都扔进去。它就是 /dev/null,非常适用于此类场景。写入其中的数据会被直接丢弃并不会占用磁盘空间,重定向很容易做到这一点。示例中,只有发往标准错误的输出被丢弃了

本文由传智教育博学谷发布。

如果本文对您有帮助,欢迎关注点赞;如果您有任何建议也可留言评论私信,您的支持是我坚持创作的动力。

转载请注明出处!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK