I'll expand on jasonwryan's answer, which explains the what but not the how or the why.
You can use the output of a command as arguments to another command. This is called command substitution.
some_text='Hello, world'
uppercased="$(echo "$some_text" | tr a-z A-Z)"
echo "Now, it's $uppercased"
However, $(ls …)
is horrible for two reasons:
- Do not parse the output of ls. It's not designed to be parsable.
"$(some-command)"
gives you a string, which is the output of that command. If you have a command that returns a list of elements, you need to parse that string into individual elements. That's fiddly and unreliable unless you can find a simple separator. For file names, you can't easily find a separator, because file names on most unices can contain every character⁰.
Note that if you write $(some-command)
rather than "$(some-command)"
, the output of the command is split into words and undergoes globbing. Until you understand the previous sentence, always put double quotes around command substitutions. When you do understand, always put double quotes unless you know why you can't put them.
Fortunately, generating the list of files that match a certain pattern is a feature that's built into the shell. It's called globbing.
echo *.pdf
To act on multiple files, you use a loop.
for x in *.pdf; do …; done
The remaining hurdle is to generate the name of the target file. The shell has some simple text manipulation commands, amongst which ${foo%SUFFIX}
to get the value of a variable with a suffix removed. (There's also ${foo#PREFIX}
.)
for x in *.pdf; do
ebook-convert "$x" "${x%.pdf}.epub"
done
Note the double quotes around variable substitutions. The reason is the same as for command substitutions.
⁰ Except a null character, but most shells can't cope with these.
The problem here isn't an issue with Linux redirection; rather, it's a fundamental misunderstanding of how the pipeline works. Redirection here isn't working because only cut is actually printing to stdout. stdout for the echo command has been piped to cut's stdin (which isn't being used in this case since a file is specified).
echo "header line" > output_file && cut -c 1-5 input_file >> output_file
is what you want, and not inelegant at all (I replaced your ;
with &&
so that the cut command will only execute if the header is successfully written; this way it won't execute if you don't have permissions to create or write to output_file).
You could also do it all in a subshell, eg.
(echo "header line"; cut -c 1-5 input_file) > output_file
but there's no real benefit to doing this and with more complex examples it can cause issues if you're not familiar with how the subshell is scoped.
If you want cut to pass stdin through to stdout you could try:
echo "header line" | cut -c 1-5 - input_file
(The dash is a common shortcut for stdin)
However, this will also perform the cut operation on stdin (resulting in a header line of "header"). It's hard to tell if this is what you want or not from the question.
Best Answer
What you've written in the first line looks like a complete command (a “(compound) list” in shell terminology), so the shell treats it as a complete command. Since there's a here-document start marker (
<<END
), the shell then reads the here-document contents, and then starts a new command. If you want to put the here-document in the middle of a list, you need to indicate to the shell that the list is not finished. Here are couple of ways.Or, of course, you can make sure the command completely fits in the first line.
The rule to remember is that the here-document contents starts after the first unquoted newline after the
<<END
indicator. For example, here's another obfuscated way of writing this script: