今天在写一个脚本时,需要将shell命令和可执行程序的输出重定向在某一个log文件中,但是遇到了点小问题,索性就研究下输出重定向到底怎么回事。
Linux系统,有一个非常重要概念,就是一切皆文件。在使用shell脚本时,系统为了能够进行接收外部输入,同时向外部输出,将三个文件始终保持在打开的状态,并使用三个文件描述符0,1,2来分别指向这三个文件,以此来完成标准输入,标准输出,标准错误输出。
标准输入:由键盘输入
标准输出:输出到屏幕
标准错误:输出到屏幕
在正常情况下,我们执行shell命令时,其输出总是标准输出或者标准错误,因此总是会将输出的信息,不论是正常信息还是报错信息,都会打印在屏幕上,但有时我们不希望这些输出打印在屏幕上,而是希望这了信息能被保存到指定的文件中,这就是输出重定向。
那么,究竟什么是标准输入/标准输出/标准错误呢?下面为大家一一介绍。
在终端输入
echo hello
hello
终端会打印hello,但是我们echo出来的hello,到底去了什么地方?
每个基于 Unix 的操作系统都有一个“输出的默认位置”的概念。由于这个短语比较啰嗦,所以大家都称它为“标准输出”或“stdout”,读作standard out。 shell(可能是 bash 或 zsh)一直在监视默认输出位置。当 shell 在标准输出那里看到新的输出时,它会将其打印在屏幕上,以便我们可以看到它。否则,shell不去监视标准输出位置,echo hello会将“hello”发送到那个默认位置,但我们却看不到。
标准输入(stdin)是命令监听信息的默认位置,尝试在终端输入cat,不添加任何参数
cat
hello
hello
1234
1234
不论输入什么,shell会将你的输入再次打印,shell怎么读取到你的输入呢,和标准输出类似,shell会一直监视默认输入位置,一有新的输入进来,shell便会把数据读进来,然后输出到stdout。
标准错误(stderr)和stdin/stdout很像,区别就是stderr是错误信息存储的地方,例如, cat一个不存在的文件
cat ttt
cat: ttt: No such file or directory
似乎和stdout没什么两样,但是我们借助管道来验证一下,在linux中,管道是将一个命令的stdout连接到另一个命令的stdin,可以使用管道符号|来完成这个操作,例如
echo "hello there"
hello there
echo "hello there" | sed "s/hello/hi/"
hi there
这里的sed是将hello替换为hi,上面命令中,echo将hello there传输到标准输出,然后通过管道将hello there将其作为标准输入传递给sed,sed对其进行操作后,再输出到标准输出。
那cat打印出的信息,到底是stdout呢还是stderr呢?看这个命令
cat ttt | sed "s/No such/hello world/"
cat: ttt: No such file or directory
如果 cat ttt打印出来的信息是stdout的话,那No such会被替换成hello world,但似乎并没有被替换到,是的,cat ttt打印出的信息,是stderr而不是stdout。管道只会接收stdout,而不会接收stderr。
这里我们终于可以知道什么是重定向了,默认情况下,我们shell执行的命令的输出一定会到stdout或者stderr,如果我们不想让信息输出到stdout或者stderr,那就要用到重定向了,我们可以使用>将输出进行重定向。
$ echo "hello world" > file
$ cat file
hello world
这行命令做了两件事
如果只是想追加内容而不是覆盖原有内容,则可以使用>>
$ echo "hello world" > file
$ cat file
hello world
$ echo "go go go" >>file
$ cat file
hello world
go go go
其中,如果>或者>>前不添加文件描述符,则默认是将标准输出重定向到file,如果想重定向stderr,则需要表示为1 > file或者2 > file
文件描述符(File descriptor)是表示输入/输出源的正整数,例如stdin是0,stdout是1,stderr是2,这些数字是由POSIX标准定义的,MacOS和Linux都实现了这个标准的一部分。
如果想将输出重定向到某一文件描述符,则需要借助>&运算符并跟上文件描述符来完成
# Redirect stdout to stdout (FD 1)
echo "hello there" >&1
hello there
# Redirect stdout to stderr (FD 2)
echo "hello there" >&2
hello there
这和上面的输出重定向到某一文件基本一样,只不过重定向的最终目标变成了stdout和stderr,让我们继续通过管道看看这两个输出有什么区别
echo "no changes" >&1 | sed "s/no/some/"
some changes
# Redirect to stderr, so it does not come through
echo "no changes" >&2 | sed "s/no/some/"
no changes
原理还是和上面一样,第一行将输出重定向到了标准输出,所以管道会将其传递给sed,第二行经echo的输出重定向到了标准错误,管道无法传递。
如果一个脚本,将输入的参数的三个参数分别重定向到了stdout stderr stdout,那么一个命令就出现了两种不同的输出,使用管道时,就无法传递全部的输出作为下个命令的输入,我们编写一个这样的command
#!/bin/bash
for f in $@; do
if [[ $f == "file2" ]]; then
echo "stderr file2" >& 2
else
echo "stdout $f"
fi
done
$ bash command file1 file2 file3
stdout file1
stderr file2
stdout file3
用管道来处理,则只会处理部分,file2是stderr,无法被管道抓取。
$ bash command file1 file2 file3 | sed "s/^/Robot says: /"
stderr file2
Robot says: stdout file1
Robot says: stdout file3
我们可以通过2>&1将标准错误重定向给标准输出,例如
cat ttt 2>&1 | sed "s/No such/hello world/"
cat: ttt: hello world file or directory
由于stderr重定向到了stdout,管道就将输出的信息传递给了sed,sed做了处理并输出到stdout。
同样的,对于command也可以这样做
dexu_tian@VM-4-10-ubuntu:~/Tmp/outputRedirect$ bash ./command file1 file2 file3 2>&1 | sed "s/std/Robot says: std/"
Robot says: stdout file1
Robot says: stderr file2
Robot says: stdout file3
比较常见的用法是将一个命令的stdout和stderr都重定向到某一个文件中,那么它的写法就应该是这样的
cmd > logfile 2>&1
cmd的stdout将会被重定向到logfile,stderr将会被重定向到stdout,由此实现了stderr和stdout被重定向到了logfile.
像2>&1一样,如果想重定向文件描述符,则需要表示为N >&M,其中N和M是文件描述符,其为1和2时,就是在重定向stdout和stdin了。如果M不是文件描述符,则使用文件名N>filename
我们可以通过这种重定向的语法,将所有的输出重定向到/dev/null, 它会吞下所有接收到的内容并且不做任何操作。
echo "hello there" >/dev/null
以上就是本文所要分享的内容,希望大家每天坚持进步~
大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje
为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar
我正在使用puppet为ruby程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这
这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][
我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test
我是rails的新手,想在form字段上应用验证。myviewsnew.html.erb.....模拟.rbclassSimulation{:in=>1..25,:message=>'Therowmustbebetween1and25'}end模拟Controller.rbclassSimulationsController我想检查模型类中row字段的整数范围,如果不在范围内则返回错误信息。我可以检查上面代码的范围,但无法返回错误消息提前致谢 最佳答案 关键是您使用的是模型表单,一种显示ActiveRecord模型实例属性的表单。c
我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file
我克隆了一个rails仓库,我现在正尝试捆绑安装背景:OSXElCapitanruby2.2.3p173(2015-08-18修订版51636)[x86_64-darwin15]rails-v在您的Gemfile中列出的或native可用的任何gem源中找不到gem'pg(>=0)ruby'。运行bundleinstall以安装缺少的gem。bundleinstallFetchinggemmetadatafromhttps://rubygems.org/............Fetchingversionmetadatafromhttps://rubygems.org/...Fe
在Cooper的书BeginningRuby中,第166页有一个我无法重现的示例。classSongincludeComparableattr_accessor:lengthdef(other)@lengthother.lengthenddefinitialize(song_name,length)@song_name=song_name@length=lengthendenda=Song.new('Rockaroundtheclock',143)b=Song.new('BohemianRhapsody',544)c=Song.new('MinuteWaltz',60)a.betwee
我是Google云的新手,我正在尝试对其进行首次部署。我的第一个部署是RubyonRails项目。我基本上是在关注thisguideinthegoogleclouddocumentation.唯一的区别是我使用的是我自己的项目,而不是他们提供的“helloworld”项目。这是我的app.yaml文件runtime:customvm:trueentrypoint:bundleexecrackup-p8080-Eproductionconfig.ruresources:cpu:0.5memory_gb:1.3disk_size_gb:10当我转到我的项目目录并运行gcloudprevie