在一个参数上运行两个命令(无需脚本)


28

如何在一个输入上执行两个命令,而无需两次键入该输入?

例如,stat命令可以告诉很多有关文件的信息,但没有指出其文件类型:

stat fileName

file命令告诉文件是什么类型:

file fileName

您可以通过以下方式在一行中执行此操作:

stat fileName ; file fileName

但是,您必须键入fileName两次。

您如何在同一输入上执行两个命令(而无需两次键入输入或输入变量)?

在Linux中,我知道如何管道输出,但是如何管道输入呢?


14
需要明确的是,这些不是“输入”,而是参数(也称为参数)。 输入可以通过<(从文件到左侧的|输入)或(从流到右侧的输入)通过管道传输。有区别。
goldilocks 2014年

6
不是您的问题的答案,而是您的问题的答案:在bash中,该yank-last-arg命令(默认快捷方式Meta-.Meta-_; Meta通常是向左键Alt)会将前一个命令行中的最后一个参数复制到当前命令行中。因此,执行后,stat fileNamefile [Meta-.]无需fileName再次键入。我总是用那个。
Dubu 2014年

2
区别在于<>重定向,而不是管道。管道连接两个进程,而重定向仅重新分配stdin / stdout。
bsd 2014年

@bdowning:是的,但是您正在重定向管道,不是吗。您可以重定向要从磁盘上的文件读取的管道的开始,也可以重定向要写入磁盘上的文件的管道的末端。仍在管道中。;-)
James Haigh 2014年

Answers:


21

这是另一种方式:

$ stat filename && file "$_"

例:

$ stat /usr/bin/yum && file "$_"
  File: ‘/usr/bin/yum’
  Size: 801         Blocks: 8          IO Block: 4096   regular file
Device: 804h/2052d  Inode: 1189124     Links: 1
Access: (0755/-rwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Context: system_u:object_r:rpm_exec_t:s0
Access: 2014-06-11 22:55:53.783586098 +0700
Modify: 2014-05-22 16:49:35.000000000 +0700
Change: 2014-06-11 19:15:30.047017844 +0700
 Birth: -
/usr/bin/yum: Python script, ASCII text executable

这在bash和中起作用zsh。这也适用,mksh并且dash仅在交互式时有效。在AT&T ksh中,这仅在和不在file "$_"同一行时才有效stat


1
!$一个不会起作用,因为!$扩展到最后历史事件的最后一个字(所以没有的stat命令以前输入的命令行)。
斯特凡Chazelas

@StéphaneChazelas:哦,是的,我的错,我删除了。
cuonglm 2014年

32

可能会为此而烦恼,这是bash括号扩展和eval的组合,似乎可以解决问题

eval {stat,file}" fileName;"

8
对不起,但是我无法抗拒
Andreas Wiese 2014年

2
大括号扩展起源于csh,而不是bashbash从复制ksh。该代码将适用于所有csh,tcsh,ksh,zsh,fish和bash。
斯特凡Chazelas

1
不幸的是,由于引号,您失去了“跳出”(自动完成)fileName的能力。
Lonniebiz 2014年

很抱歉,但我无法抗拒或者
tomd

对不起,这是什么问题eval?(指的是最高评论)
user13107'3

28

使用zsh,您可以使用匿名函数:

(){stat $1; file $1} filename

使用eslambda:

@{stat $1; file $1} filename

您也可以这样做:

{ stat -; file -;} < filename

(做stat首先作为file将更新访问时间)。

我会做:

f=filename; stat "$f"; file "$f"

但是,这就是变量的用途。


3
{ stat -; file -;} < filename是魔术。stat必须检查其stdin是否已连接到文件并报告文件的属性?大概不推进stdin上的指针,因此允许file嗅探stdin内容
iruvar

1
@ 1_CR,是的。stat -告诉其在fd 0上stat执行一次fstat()
StéphaneChazelas 14年

4
{ $COMMAND_A -; $COMMAND_B; } < filename如果$ COMMAND_A实际上读取任何输入,则该变体在一般情况下将不起作用。例如,{ cat -; cat -; } < filename将只打印文件名的内容一次。
Max Nanasy 2014年

2
stat -从coreutils-8.0(2009年10月)开始特别处理,在此之前的版本中,您会得到一个错误(除非您-从以前的实验中调用了一个文件,在这种情况下,您将得到错误的结果...)
Mr .spuratic 2014年

1
@ mr.spuratic。是的,BSD,IRIX和zsh统计信息也无法识别它。使用zsh和IRIX stat,可以stat -f0代替使用。
斯特凡Chazelas

9

在我看来,所有这些答案或多或少都像是脚本。对于真正不涉及脚本的解决方案,并假设您坐在键盘上键入外壳,您可以尝试:

stat somefile Enter
文件Esc_   Enter

至少在vi和emacs模式下,至少在bash,zsh和ksh中,按Escape键,然后下划线将前一行的最后一个单词插入当前行的编辑缓冲区。

或者,您可以使用历史记录替换:

stat somefile Enter
文件!$Enter

在bash中,!$当解析当前命令行时,zsh,csh,tcsh和大多数其他Shell 被替换为前一个命令行的最后一个单词。


zsh / bash中还有哪些其他可用的选项Esc _?您可以链接官方文档吗?
Abhijeet Rastogi 2014年


1
zsh:linux.die.net/man/1/zshzle并搜索“ insert-last-word”
godlygeek

1
@shadyabhi ^ bash和zsh的标准键绑定的链接。请注意,bash只是使用readline,因此(大多数)bash可以在使用readline库的任何应用程序中使用。
godlygeek

3

我发现合理地做到这一点的简单方法是使用xargs,它需要一个文件/管道并将其内容转换为程序参数。可以与tee结合使用,tee分割流并将其发送到两个或多个程序,在您的情况下,您需要:

echo filename | tee >(xargs stat) >(xargs file) | cat

与许多其他答案不同,这将在Linux下的bash和大多数其他shell中起作用。我将建议这是一个很好的变量用例,但是如果您绝对不能使用一个变量,则这是仅通过管道和简单实用程序(假设您没有特别花哨的外壳)来做到这一点的最佳方法。

另外,您可以对任意数量的程序执行此操作,只需在发球台之后以与这两个程序相同的方式添加它们即可。

编辑

正如评论中所建议的那样,此答案有两个缺陷,第一个是输出隔行扫描的潜力。可以这样修复:

echo filename | tee >(xargs stat) >(( wait $!; xargs file )) | cat

这将强制命令依次运行,并且输出将永远不会交错。

第二个问题是避免某些Shell中不可用的进程替换,这可以通过使用tpipe而不是tee来实现:

echo filename | tpipe 'xargs stat' | ( wait $!; xargs file )

这应该可以移植到几乎所有外壳(我希望),并且可以解决另一个建议中的问题,但是由于我的当前系统没有可用的tpipe,因此我正在从内存中编写最后一个。


1
进程替换是ksh功能,仅适用于ksh(不适用于公共领域的变体)bashzsh。请注意,statfile会同时运行,所以可能他们的输出将被交织在一起(我假设你使用| cat武力输出缓冲,以限制效果?)。
斯特凡Chazelas

@StéphaneChazelas您对cat的观点是正确的,使用cat时我从未设法产生隔行输入的情况。但是,我将暂时尝试一些方法以产生一个更可移植的解决方案。
价格

3

这个答案与其他几个类似:

stat 文件名   &&文件!#:1

与其他答案会自动重复一个命令的最后一个参数不同,此答案需要您计算:

ls -ld 文件名   &&文件!#:2

与其他一些答案不同,这不需要引号:

stat "useless cat"  &&  file !#:1

要么

echo Sometimes a '$cigar' is just a !#:3.

2

这类似于一对夫妇其他的答案,但比更轻便这一项 ,比简单的多这一个

sh -c'stat“ $ 0”; 文件“ $ 0”' 文件名

要么

sh -c'stat“ $ 1”; 文件“ $ 1”'sh 文件名

其中sh分配给$0。尽管要多输入三个字符,但最好使用这种(第二种)形式,因为这$0是您为该内联脚本指定的名称。例如,它在该脚本的外壳程序的错误消息中使用,例如何时filestat由于某种原因找不到或无法执行。


如果你想做

stat 文件名1 文件名2 文件名3 …; 文件1 文件名2 文件名3

对于任意数量的文件,请执行

sh -c'stat“ $ @”; 文件“ $ @”'sh 文件名1 文件名2 文件名3

之所以有效"$@"是因为等同于"$1" "$2" "$3" …


$0是您要为该内联脚本指定的名称。例如,该脚本的外壳在错误消息中使用了它。就像当filestat无法找到或因任何原因无法执行。因此,您想要在这里有意义的东西。如果没有启发,请使用sh
斯特凡Chazelas

1

我认为您提出了错误的问题,至少对于您尝试做的事情。 statfile命令使用的参数是文件的名称,而不是文件的内容。稍后,该命令将读取由您指定的名称标识的文件。

管道应该有两个末端,一个末端用作输入,另一末端用作输出,这很有意义,因为您可能需要沿途使用不同的工具进行不同的处理,直到获得所需的结果。说您知道如何管道输出但不知道如何管道输入在原则上是不正确的。

在您的情况下,您根本不需要管道,因为您必须以文件名的形式提供输入。即使这些工具(statfile)会读入stdin,管道在这里也不再相关,因为在进入第二个工具时,任何工具都不应更改输入。


1

这应该适用于当前目录中的所有文件名。

sh -c "$(printf 'stat -- "$1";file -- "$1";shift%.0b\n' *)" -- *

1
假设文件名不包含双引号,反斜杠,美元,反引号,}...字符,并且不以开头-。某些printf实现(zsh,bash,ksh93)具有%q。有了zsh,这可能是:printf 'stat %q; file %1$q\n' ./* | zsh
斯特凡Chazelas

@StephaneChezales-所有这些现在都已修复,除了可能使用硬引号。我可以用一个来处理它sed s///,但是它涉及范围-如果人们将其放在文件名中,则应该使用Windows。
mikeserv 2014年

@StéphaneChazelas-没关系-我忘记了那些东西的处理方式。
mikeserv

严格来说,这不是对以下问题的答案:“如何在同一个输入上执行两个命令(而不输入两次……)?”(强调)。您的答案适用于当前目录中的所有文件- 包括*两次。你不妨说stat -- *; file -- *
Scott

@Scott-输入未键入两次- *展开。所述膨胀*加上两个命令用于在每一个自变量 *是输入。看到?printf commands\ %s\;shift *
mikeserv

1

这里有一些对“输入”的不同引用,因此我将给出一些首先要理解它的场景。 为了以最短的形式快速回答问题

stat testfile < <($1)> outputfile

上面的代码将对测试文件执行统计,将其重定向到STDOUT并将其包含到下一个特殊功能(<()部分)中,然后将最终结果输出到新文件中(输出文件)。调用该文件,然后使用bash内置文件对其进行引用(此后每次$ 1,直到您开始一组新的指令为止)。

您的问题很好,有几种答案和解决方法,但确实会随着您的具体操作而改变。

例如,您也可以循环执行,这非常方便。在伪代码心态中,此方法的常见用法是:

run program < <($output_from_program)> my_own.log

吸收并扩展该知识,可以创建以下内容:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

这将在当前目录中执行一个简单的ls -A,然后告诉何时遍历ls -A的每个结果到(在这很棘手!)grep“ thatword”在每个结果中,并且仅执行前一个printf(红色),如果它实际上找到其中带有“ thatword”的文件。它还会将grep的结果记录到一个新的文本文件files_that_matched_thatword中。

输出示例:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

index.html

所有这些仅打印ls -A结果,没什么特别的。这次为grep添加一些东西:

echo "thatword" >> newfile

现在重新运行它:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

files_that_matched_thatword  index.html  newfile

Found a file: newfile:thatword

尽管也许比您目前所希望的要穷得多,但我相信保留此类便笺便会在将来的工作中为您带来更多好处。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.