1

I'm struggling to understand command redirection/reuse...

I understand there's the <(...) <(...) methodology and the $( ... && ... ) techniques for combining output. But I don't really fully understand what the difference is (I realize that the (...) dumps you inside a new shell, causing you to potentially jump dirs and lose any vars you haven't exported, but I'm unsure of how it effects the general redirection scheme) and I'm still quite confused as to how to do one-to-many redirection after skimming over the examples and instructions in:
Advanced Bash Scripting Guide 1
Advanced Bash Scripting Guide 2

My own attempts to play around with it have mostly resulted in "ambiguous redirect" errors.

For example, let's say I want to do a one liner given by the pseudocode below

    CMD 1 && CMD 2 && CMD 3 --> (1)  
    CMD 4 (1) --> (2)
    CMD 5 (1) --> CMD 6 --> (3)
    CMD 7 (2) && CMD 8 (3) --> (4)
    CMD 9 (2) --> (5)
    CMD 10 (2) (3) -->(6)
    VAR= echo (4) && echo (5) && echo (6)

Or as a process diagram

CMD 1 +CMD 2 && CMD 3 
    |\
    | ---> CMD 5 ------> CMD 6-----\
    V                     /         V
    CMD 4 ----------------u--------> CMD 10
     | \                  V           /
     |   -------->CMD 7 + CMD 8      /
     V             |                /
    CMD 9          |               /
      \            |              /
       \           V             /
        --------> VAR <----------

Where outputs are designated as -->; storage for reuse in another op is given by -->(#); and combination operations are given by &&.

I currently have no idea how to do this in a single line without redundant code.

I want to truly master command redirection so I can make some powerful one-liners.

Hopefully that's clear enough... I could come up with proof of concept examples for the commands, if you need them, but the pseudocode should give you the idea.

Nitesh
  • 2,286
  • 2
  • 43
  • 65
Jason R. Mick
  • 5,177
  • 4
  • 40
  • 69
  • I don't understand what you mean when you say there's the <(...) methodology for combining output, since <(...) is used for input, and you cannot have multiple < for the same command. – huelbois Mar 14 '12 at 08:07
  • 1
    Oh, maybe you mean something like 'cat <(cmd 1) <(cmd 2) > out3' ? – huelbois Mar 14 '12 at 08:13
  • I mean "output" as in the sense <(1) <(2) "Combine the output of 1 and 2"... my apologies for the ambiguity. – Jason R. Mick Mar 14 '12 at 08:13
  • Bit confused you talk of pipes and redirection, but your pseudo code is about command substitution. – Dunes Mar 14 '12 at 08:14
  • yep... or to give a more advanced example `sort <(cd $CURR_DIR && find . -type f -ctime $FTIME) \ <(cd $CURR_DIR && find . -type f -atime $FTIME) \ <(cd $CURR_DIR && find . -type f -mtime $FTIME) | uniq` – Jason R. Mick Mar 14 '12 at 08:15
  • Well, I do know that with `<(...) <(...)` it may dump you in a different directory, and you lose any non-exported env. vars (as it is a full subshell versus `$( ... && ... )` where you stay in the directory and have all your script-level vars)... I guess, I'm curious if there's other differences and how to 1-to-many output. – Jason R. Mick Mar 14 '12 at 08:17
  • "Pipes" is probably the wrong word... but by redirection, I mean redirect input from multiple sources together at some points, and apart to multiple outputs at other points... – Jason R. Mick Mar 14 '12 at 08:19
  • @Dunes, correct.. command substitution and then redirecting output from those commands to multiple commands, then merging that output together... so "command substitution" and "redirection" are the proper keywords I believe... – Jason R. Mick Mar 14 '12 at 08:22
  • Are you sure that you really want to put on only one line something that you can express in no less than seven lines? What about readability and maintainability? – mouviciel Mar 14 '12 at 08:39
  • True, if there was some way to unambiguously assign a sub operation to a #ed stream, that would be good for eliminating confusion. Of course you could also draw a diagram like above to clarify what the op does... – Jason R. Mick Mar 14 '12 at 08:50

3 Answers3

2

There's some kind of beginning of answer to your problem in this post: How can I send the stdout of one process to multiple processes using (preferably unnamed) pipes in Unix (or Windows)?

Using tee and >(command). But your example is rather sophisticated, and it's hard to imagine a one-line solution, without temp storage (var or file), by only combining commands (the Unix way, yes!).

I mean, if you agree to have several commands launched sequentially, it may be easier (but it won't be a one-liner any longer)...

Even writing this kind of expression with a more complex language (say python) would be complicated.

Community
  • 1
  • 1
huelbois
  • 6,762
  • 1
  • 19
  • 21
  • Cool, good find `tee` is a step in the right direction... I wonder if there's anything more multi-stream that you can use? I added a diagram to describe the kind of complicated mess of commands I have in mind :) – Jason R. Mick Mar 14 '12 at 08:47
  • The part I don't know how to solve is having at the same time a split (like cmd 4 => cmd7+cmd8 AND cmd 9) and a merge (VAR < cmd9 AND cmd7+cmd8). Each can be done easily individually, but both splitting and merging at the same time..? No clue. Maybe toying with alternative file descriptors coud help ?(like >&3, etc) – huelbois Mar 14 '12 at 09:08
  • Well, in fact you can do the part I told just above, using : cmd 4 | tee >(cmd 7 && cmd 8) >(cmd 9) > /dev/null | cmd 10. Using this kind of construct, you could solve parts of your problem EXCEPT the merging of CMD 6 and CMD 4 into CMD 7 + CMD 8. It's the same kind of problem you have when you try to implement some specific (but frequent) kinds of processing with a language not implementing 'goto'. When you only have if/else, you have to rely on temp vars to store temp state and alter your code (please no flame war, just a note) – huelbois Mar 14 '12 at 10:00
2

In reply to your comment:

sort <(cd $CURR_DIR && find . -type f -ctime $FTIME) \ 
    <(cd $CURR_DIR && find . -type f -atime $FTIME) \ 
    <(cd $CURR_DIR && find . -type f -mtime $FTIME) | uniq

can be written as (which I believe is clearer)

(find . -type f -ctime $FTIME && find . -type f -atime $FTIME \
    && find . -type f -mtime $FTIME) | sort | uniq

Given three programs one which produces "a" or "z" as output. Produce a string that contains the sorted output and also the unique output as a one liner:

mkfifo named_pipe{1,2,3}; (echo z ; echo a ; echo z) > named_pipe1 & \
    tee < named_pipe1 >(sort > named_pipe2) | sort | uniq > named_pipe3 & \
    output=$(echo sorted `cat named_pipe2`; echo unique `cat named_pipe3`); \
    rm named_pipe{1,2,3}

Produces sorted a z z unique a z

One thing you may notice about this solution is that its been split up so that each grouping of commands has its own line. I suppose what I'm getting at here is that one liners can be cool, but often clarity is better.

The mechanism by how this works is use of a program called tee and named pipes. A named pipe is exactly like an anonymous pipe eg. cat words.txt | gzip, except that it can be referenced from the file system (but no actual data is written to the file system). Note that writing to a named pipe will block until another process is reading from the named pipe. I've been gratuitous with my use of pipes here, just so you can get a feel of how to use them.

Tee, as noted by others, can replicate input to several outputs.

Dunes
  • 37,291
  • 7
  • 81
  • 97
  • If I am not wrong: The `find` *example* can be done without any redirecting or subshells at all by using a simple: `find ${CURR_DIR} -type f \( -ctime ${FTIME} -o -atime ${FTIME} -o -mtime ${FTIME} \) -printf "./%P\n"` where `-printf "./%P\n"` mimics the `cd ${CURR_DIR} && find .` part. No need to sort. No need to filter. (I know it does not answer the initial question, but it is - as I believe - even more clear. 8) – mariux Apr 02 '13 at 21:11
1

You have several different problems to solve.

  1. You have one output that you need as input for several other commands. You can solve this with tee >(cmd1) | cmd2. See this answer: How can I send the stdout of one process to multiple processes using (preferably unnamed) pipes in Unix (or Windows)?

    An alternative is to create a file. I usually prefer the file approach because it allows to debug the final script. To avoid leaving a mess, create a temporary work directory which you delete with trap "..." EXIT

  2. You need to combine the outputs of several commands as the input to a single command. Use sub shells here: ( CMD 1 ; CMD 2 ) | CMD 3 combines the output of 1 and 2 as input for 3.

  3. You need a mix of the two above. You can create additional file descriptors but each can be read only once. Use cat to combine them and tee to create copies.

This should all be possible but there is a drawback: If something doesn't work or goes wrong, you will never be able to find the bug.

Therefore: Create a work directory and use files. If you put this into /tmp, the data won't be written to disk (on modern Linux systems, /tmp is a ram disk), this will behave like pipes except when you have huge amounts of data plus you will be able to maintain the script.

Community
  • 1
  • 1
Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820