17

I'd like to concatenate the output from echo with content of a file. I've tried the following comand:

echo "abc" | cat 1.txt > 2.txt

but the 2.txt file only contains the content from 1.txt. Why doesn't it work?

1
  • 3
    Programs like cat only read from standard input if they don't have any filename arguments.
    – Barmar
    Jan 13, 2018 at 17:22

7 Answers 7

28

It does not work because the cat program in your pipe sequence was not instructed to read the echo program's output from standard input.

You can use - as a pseudo file name to indicate standard input to cat. From man cat on an msys2 installation:

EXAMPLES
       cat f - g
              Output f's contents, then standard input, then g's contents.

So try

echo "abc" | cat - 1.txt > 2.txt

instead.

23

As noted by others, your original command fails because cat 1.txt disregards its standard input. Either indicate that it should be its first argument (cat - 1.txt), or use block redirection to redirect echo abc and cat 1.txt together. To wit:

{ echo abc; cat 1.txt; } > 2.txt 

Relevant excerpt from the manual (man bash):

Compound Commands
A compound command is one of the following.  In most cases a list in a command's description may be separated from the rest of the command by one or more newlines, and may be followed by a newline in place of a semicolon.

(list)

    list is executed in a subshell environment (see COMMAND EXECUTION ENVIRONMENT below). Variable assignments and builtin commands that affect the shell's environment do not remain in effect after the command completes.  The return status is the exit status of list.

{list; }

    list is simply executed in the current shell environment.  list must be terminated with a newline or semicolon.  This is known as a group command.  The return status is the exit status of list.  Note that unlike the metacharacters ( and ), { and } are reserved words and must occur where a reserved word is permitted to be recognized.  Since they do not cause a word break, they must be separated from list by whitespace or another shell metacharacter.

The first option (subshell environment) has a bunch of side effects, most if not all of which are irrelevant to your scenario; however, if redirecting a bunch of commands' output is all you need, then Option #2 here (group command) is preferred.

9
( echo "abc"; cat 1.txt ) > 2.txt

You piped the echo output to cat, but cat had no use for input, and ignored it. Now the commands run one after the other, and their output is grouped (the parentheses) and directed into 2.txt.

5
  • 1
    This answer can be improved by explaining how it works and why the OP's original attempt does not work.
    – Bob
    Jan 13, 2018 at 13:01
  • OK, gave it a try. Jan 13, 2018 at 13:06
  • 6
    A subshell is not expressly required here, perhaps it would be better to use a group command. See my answer for more details.
    – Nubarke
    Jan 13, 2018 at 15:13
  • Ah, but I'd never use bash. Jan 13, 2018 at 15:52
  • 1
    @GerardH.Pille: While Daniel’s answer quotes from bash(1), it describes behavior that every POSIX-compliant shell must support. Jan 13, 2018 at 18:44
3

Your command cat 1.txt doesn't do anything with the output of echo "abc".

Instead: (echo "abc"; cat 1.txt) > 2.txt will write the output of both commands into 2.txt.

1
  • 1
    This answer can be improved by explaining how the suggested alternative works.
    – Bob
    Jan 13, 2018 at 13:01
3

In any POSIX shell you could use command substitution to use cat file as input for echo:

echo $(cat file1.txt) "This Too"

In Bash you could use process substitution and use echo as another "file" for cat, as in:

cat file1.txt <(echo This Too)

and then pipe or redirect the output as you see fit.

Like every other answer says, cat ignores stdin if it's got a file to look at. (I do like Daniel & mvw's answers too, +1 for them)

2
  • 2
    This answer can be improved by explaining why the OP's original attempt does not work (and therefore why this is necessary instead).
    – Bob
    Jan 13, 2018 at 13:02
  • 1
    The <(command) construction is a bash extension. It is not POSIX, so you can't rely on it in scripts that are supposed to be executed with /bin/sh. Jan 13, 2018 at 20:37
1

It is just a guess, but it looks like you have a repeatable process, which is a good candidate for an alias or a function. Aliases are usually shortcuts for invoking Bash commands, such as abbreviation, on a single input stream as its argument(s).

alias hg="history | grep"

However, in this case, a function would be more readable, since you are combining multiple, discrete (2) input streams as well as multiple bash commands. You have two arguments, the first being a string and the other a filepath. In the end, you want the result to be written to the standard output stream.

From a CLI prompt, type this:

# ecat()
{
echo ${1}
cat ${2}
}

Your function is named ecat, which is memorable.

Now you can invoke as

ecat "abc" 1.txt

To append, simply supply a different output destination to standard output:

ecat "abc" 1.txt >> 2.txt

The append redirection operator '>>' will add the output to the end of the specified file.

If you like it, then append to your ~/.bashrc file for reuse.

declare -f ecat >> ~/.bashrc

That also means you can decorate, etc., within your function definition.

It is also a good idea to protect files from being overwritten by adding this to your ~/.bashrc file file:

set noclobber
2
  • 2
    For your function to behave properly, you should use "$1" and "$2". In this context, the curly braces don't do any good. See ${name} doesn’t mean what you think it does …. Jan 13, 2018 at 19:22
  • 1
    How is the noclobber suggestion responsive to the question at hand? (I'm also very much unconvinced that it's good advice -- modifying global behavior tends to break scripts/advice/practices built with defaults in mind). Jan 14, 2018 at 18:37
0

The following code will also work:

echo abc >> 1.txt && cat 1.txt > 2.txt

The output of echo abc will be appended to 1.txt with >> After that the && will tell it to run the next set of commands which will cat 1.txt and then output it to 2.txt. Basically, it appends the output of the first command into 1.txt and then sends the output of 1.txt into 2.txt.

If you want the abc to appear first, you can use the following code:

echo abc >> 2.txt && cat 1.txt >> 2.txt
8
  • 2
    This will (1) put abc at the bottom of 2.txt; the question wants it at the top.   (2) modify the 1.txt file; this is unacceptable. Jan 13, 2018 at 18:16
  • Where does it say that? Ringger81 never specified where in 2.txt he wanted abc to appear. You are assuming things that the question never states. Jan 13, 2018 at 18:50
  • (1) OK, you raise a valid point on #1. While the question mentions “output from echo” before “content of a file” in the text, and then puts the echo before the cat in the sample command, I suppose that it never exactly, explicitly, says that it wants the output from echo before the content of the input file. It just seems that most people interpreted it that way. (2) 1.txt is an input file. The question doesn’t say anything about modifying 1.txt. The fact that input files should not be modified goes without saying. Jan 13, 2018 at 19:13
  • 1
    @NasirRiley Your second option is not equivalent to what the OP is looking for, since it will not overwrite 2.txt if it already exists. It should be echo abc > 2.txt && cat 1.txt >> 2.txt.
    – Chris
    Jan 14, 2018 at 2:00
  • 1
    Opening a file, appending to it, closing it, opening it again, appending to it again... why do that rather than just open it once and keep the redirection in place for both operations? Jan 14, 2018 at 18:38

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .