Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

How can I perform two commands on one input, without typing that input twice?

For example, the stat command tells a lot about a file, but doesn't indicate its file type:

stat fileName

The file command, tells what type a file is:

file fileName

You can perform this in one line this way:

stat fileName ; file fileName

However, you have to type the fileName twice.

How can you execute both commands on the same input (without typing the input or a variable of the input twice)?

In Linux, I know how to pipe outputs, but how do you pipe inputs?

share|improve this question
11  
Just to be clear, those aren't "inputs", they're arguments (aka parameters). Input can be piped via < (input from file to the left side) or | (input from stream to the right side). There's a difference. –  goldilocks yesterday
4  
Not an answer to your question, but maybe an answer to your problem: In bash, the yank-last-arg command (default shortcut Meta-. or Meta-_; Meta often being the left Alt key) will copy the last parameter from the preceding command line into the current one. So, after executing stat fileName you type file [Meta-.] and do not have to type that fileName again. I always use that. –  Dubu 10 hours ago
    
And the difference is that < and > are redirection, not piping. A pipeline connects two processes, while redirection simply reassigns stdin/stdout. –  bdowning 1 hour ago
add comment

8 Answers

up vote 18 down vote accepted

With zsh, you can use anonymous functions:

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

With es lambdas:

@{stat $1; file $1} filename

You could also do:

{ stat -; file -;} < filename

(doing the stat first as the file will update the access time).

I'd do:

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

though, that's what variables are for.

share|improve this answer
    
{ stat -; file -;} < filename (the dashes corrupt the output of the stat command slightly - having blank File output). I see they're necessary, but are they doing? –  Lonniebiz yesterday
2  
{ stat -; file -;} < filename is magic. stat must be checking to see that its stdin is connected to a file and reporting the file's attributes? Presumably not advancing the pointer on stdin therefore allowing file to sniff stdin contents –  1_CR yesterday
1  
@1_CR, yes. stat - tells stat to do an fstat() on its fd 0. –  Stéphane Chazelas yesterday
1  
The { $COMMAND_A -; $COMMAND_B; } < filename variant won't work in the general case if $COMMAND_A actually reads any input; e.g. { cat -; cat -; } < filename will print the contents of filename only once. –  Max Nanasy 22 hours ago
1  
stat - is handled specially since coreutils-8.0 (Oct 2009), in versions prior to that you will get an error (unless you have a file called - from a previous experiment, in which case you'll get the wrong results...) –  mr.spuratic 9 hours ago
show 1 more comment

Probably going to get my knuckles rapped for this, here's a hacky combination of bash brace expansion and eval that seems to do the trick

eval {stat,file}" fileName;"
share|improve this answer
5  
Sorry, but I cannot resist. –  Andreas Wiese yesterday
1  
brace expansion originated in csh, not bash. bash copied it from ksh. That code will work in all of csh, tcsh, ksh, zsh, fish and bash. –  Stéphane Chazelas yesterday
add comment

Here is another way:

$ stat filename && file "$_"

Example:

$ 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

That works in bash and zsh. That also works in mksh and dash but only when interactive. In AT&T ksh, that only works when the file "$_" is on a different line from the stat one.

share|improve this answer
    
The !$ one won't work because !$ expands to the last word of the last history event (so of the previously entered command line, not of the stat command). –  Stéphane Chazelas yesterday
    
@StéphaneChazelas: Oh, yes, my mistake, I deleted it. –  Gnouc yesterday
add comment

I think you are asking a wrong question, at least for what you are trying to do. stat and file commands are taking parameters which is the name of a file and not the content of it. It is later on the command which does reading of the file identified by the name you are specifying.

A pipe is supposed to have two ends, one used as input and another one as output and this make sense as you might need to do different processing along the way with different tools until you get needed result. Saying that you know how to pipe outputs but don't know how to pipe inputs is not correct in principle.

In your case you don't need a pipe at all since you have to provide the input in a form of a file name. And even if those tools (stat and file) would read stdin, pipe is not relevant here again as input should not be altered by any of the tool when it gets to the second one.

share|improve this answer
add comment

The way I have found to do this reasonably simply is using xargs, it takes a file / pipe and converts its contents into a program arguments . This can be combined with tee which splits a stream and sends it to two or more programs, In your case you need:

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

Unlike many of the other answers this will work in bash and most other shells under Linux. I shall suggest this is a good use case of a variable but if you absolutely cannot use one this is the best way to do it only with pipes and simple utilities (assuming you do not have a particularly fancy shell).

Additionally you can do this for any number of programs by simply adding them in the same manner as these two after the tee.

EDIT

As suggested in the comments there are a couple of flaws in this answer, the first is the potential for output interlacing. This can be fixed as so:

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

This will force the commands to run in turn and output will never be interlaced.

The second issue is avoiding process substitution which is not available in some shells, this can be achieved by using tpipe instead of tee as so:

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

This should be portable to nearly any shell (I hope) and solves the issues in the other commend, however I am writing this last one from memory as my current system does not have tpipe available.

share|improve this answer
1  
Process substitution is a ksh feature that only works in ksh (not the public domain variants), bash and zsh. Note that stat and file will run concurrently, so possibly their output will be intertwined (I suppose you're using | cat to force output buffering to limit that effect?). –  Stéphane Chazelas yesterday
    
@StéphaneChazelas You are correct about cat, when using it I have not managed to ever produce a case of interlaced input when using it. However I am going to try something to produce a more portable solution momentarily. –  Vality 23 hours ago
add comment

All of these answers seem like scripting to me, to a greater or lesser extent. For a solution that really involves no scripting, and assumes that you're sitting at the keyboard typing into a shell, you can try:

stat somefile Enter
file Esc_   Enter

In bash, zsh, and ksh at least, in both vi and emacs modes, pressing Escape and then underscore inserts the last word from the previous line into the edit buffer for the current line.

Or, you can use history substitution:

stat somefile Enter
file !$ Enter

In bash, zsh, csh, tcsh, and most other shells, !$ is replaced with the last word of the previous command line when the current command line is parsed.

share|improve this answer
add comment

This should work for all file names in the current directory.

sh -c "$(printf 'stat -- "$1";file -- "$1";shift%.0b\n' *)" -- *
share|improve this answer
1  
Assuming the file names don't contain double quote, backslash, dollar, backtick, }... characters and don't start with -. Some printf implementations (zsh, bash, ksh93) have a %q. With zsh, that could be: printf 'stat %q; file %1$q\n' ./* | zsh –  Stéphane Chazelas 23 hours ago
    
@StephaneChezales - all of that is fixed now except for the possibility of the hardquote. I could handile that with a single sed s/// I guess, but its about of scope - and if people are putting that in their filenames they should be using windows. –  mikeserv 23 hours ago
    
@StéphaneChazelas - nevermind - I forgot how easy those were to handle. –  mikeserv 23 hours ago
add comment

This answer is similar to a couple of the others:

stat filename  &&  file !#:1

Unlike the other answers, which automatically repeat the last argument from the previous command, this one requires you to count:

ls -ld filename  &&  file !#:2

Unlike some of the other answers, this does not require quotes:

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

or

echo Sometimes a '$cigar' is just a !#:3.
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.