In bash tee is making function variables local, how do I escape this?
This is a variant of BashFAQ #24.
var 2>&1 | tee $LOGV
...like any shell pipeline, has the option to run the function var
inside a subprocess -- and, in practice, behaves this way in bash. (The POSIX sh specification leaves the details of which pipeline components, if any, run inside the parent shell undefined).
Avoiding this is as simple as not using a pipeline.
var > >(tee "$LOGV") 2>&1
...uses process substitution (a ksh extension adopted by bash, not present in POSIX sh) to represent the tee
subprocess through a filename (in the form /dev/fd/##
on modern Linux) which output can be redirected to without moving the function into a pipeline.
If you want to ensure that tee
exits before other commands run, use a lock:
#!/bin/bash
logv=/tmp/log
collect_var() {
echo "value for var:"
read -re var
}
collect_var > >(logv="$logv" flock "$logv" -c 'exec tee "$logv"') 2>&1
flock "$logv" -c true # wait for tee to exit
echo "This is var: $var"
Incidentally, if you want to run multiple commands with their output being piped in this way, you should invoke the tee
only once, and feed into it as appropriate:
#!/bin/bash
logv=/tmp/log
collect_var() { echo "value for var:"; read -re var; }
exec 3> >(logv="$logv" flock "$logv" -c 'exec tee "$logv"') # open output to log
collect_var >&3 2>&3 # run function, sending stdout/stderr to log
echo "This is var: $var" >&3 # ...and optionally run other commands the same way
exec 3>&- # close output
flock "$logv" -c true # ...and wait for tee to finish flushing and exit.
Using variables inside a bash heredoc
In answer to your first question, there's no parameter substitution because you've put the delimiter in quotes - the bash manual says:
The format of here-documents is:
<<[-]word
here-document
delimiterNo parameter expansion, command substitution, arithmetic expansion, or
pathname expansion is performed on word. If any characters in word are
quoted, the delimiter is the result of quote removal on word, and the
lines in the here-document are not expanded. If word is unquoted, all
lines of the here-document are subjected to parameter expansion, command substitution, and arithmetic expansion. [...]
If you change your first example to use <<EOF
instead of << "EOF"
you'll find that it works.
In your second example, the shell invokes sudo
only with the parameter cat
, and the redirection applies to the output of sudo cat
as the original user. It'll work if you try:
sudo sh -c "cat > /path/to/outfile" <<EOT
my text...
EOT
read stdin in function in bash script
If the question is How do I pass stdin to a bash function?
, then the answer is:
Shellscript functions take stdin the ordinary way, as if they were commands or programs. :)
input.txt:
HELLO WORLD
HELLO BOB
NO MATCH
test.sh:
#!/bin/sh
myfunction() {
grep HELLO
}
cat input.txt | myfunction
Output:
hobbes@metalbaby:~/scratch$ ./test.sh
HELLO WORLD
HELLO BOB
Note that command line arguments are ALSO handled in the ordinary way, like this:
test2.sh:
#!/bin/sh
myfunction() {
grep "$1"
}
cat input.txt | myfunction BOB
Output:
hobbes@metalbaby:~/scratch/$ ./test2.sh
HELLO BOB
How to cat EOF a file containing code?
You only need a minimal change; single-quote the here-document delimiter after <<
.
cat <<'EOF' >> brightup.sh
or equivalently backslash-escape it:
cat <<\EOF >>brightup.sh
Without quoting, the here document will undergo variable substitution, backticks will be evaluated, etc, like you discovered.
If you need to expand some, but not all, values, you need to individually escape the ones you want to prevent.
cat <<EOF >>brightup.sh
#!/bin/sh
# Created on $(date # : <<-- this will be evaluated before cat;)
echo "\$HOME will not be evaluated because it is backslash-escaped"
EOF
will produce
#!/bin/sh
# Created on Fri Feb 16 11:00:18 UTC 2018
echo "$HOME will not be evaluated because it is backslash-escaped"
As suggested by @fedorqui, here is the relevant section from man bash
:
Here Documents
This type of redirection instructs the shell to read input from the
current source until a line containing only delimiter (with no
trailing blanks) is seen. All of the lines read up to that point are
then used as the standard input for a command.The format of here-documents is:
<<[-]word
here-document
delimiterNo parameter expansion, command substitution, arithmetic expansion,
or pathname expansion is performed on word. If any characters in word
are quoted, the delimiter is the result of quote removal on word, and
the lines in the here-document are not expanded. If word is
unquoted, all lines of the here-document are subjected to parameter
expansion, command substitution, and arithmetic expansion. In the
latter case, the character sequence\<newline>
is ignored, and\
must be used to quote the characters\
,$
, and`
.
How to store standard error in a variable
It would be neater to capture the error file thus:
ERROR=$(</tmp/Error)
The shell recognizes this and doesn't have to run 'cat
' to get the data.
The bigger question is hard. I don't think there's an easy way to do it. You'd have to build the entire pipeline into the sub-shell, eventually sending its final standard output to a file, so that you can redirect the errors to standard output.
ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 )
Note that the semi-colon is needed (in classic shells - Bourne, Korn - for sure; probably in Bash too). The '{}
' does I/O redirection over the enclosed commands. As written, it would capture errors from sed
too.
WARNING: Formally untested code - use at own risk.
How to quotes in bash function parameters?
The reason this happens is because bash interprets the arguments, as you thought. The quotes simply aren't there any more when it calls the function, so this isn't possible. It worked in DOS because programs could interpret the command line themselves, not that it helps you!
bash functions: enclosing the body in braces vs. parentheses
Why are braces used by default to enclose the function body instead of parentheses?
The body of a function can be any compound command. This is typically { list; }
, but three other forms of compound commands are technically allowed: (list)
, ((expression))
, and [[ expression ]]
.
C and languages in the C family like C++, Java, C#, and JavaScript all use curly braces to delimit function bodies. Curly braces are the most natural syntax for programmers familiar with those languages.
Are there other major downsides (*) to using parentheses instead of braces (which might explain why braces seem to be preferred)?
Yes. There are numerous things you can't do from a sub-shell, including:
- Change global variables. Variables changes will not propagate to the parent shell.
- Exit the script. An
exit
statement will exit only the sub-shell.
Starting a sub-shell can also be a serious performance hit. You're launching a new process each time you call the function.
You might also get weird behavior if your script is killed. The signals the parent and child shells receive will change. It's a subtle effect but if you have trap
handlers or you kill
your script those parts not work the way you want.
When shall I use curly braces to enclose the function body, and when is it advisable to switch to parentheses?
I would advise you to always use curly braces. If you want an explicit sub-shell, then add a set of parentheses inside the curly braces. Using just parentheses is highly unusual syntax and would confuse many people reading your script.
foo() {
(
subshell commands;
)
}
Pipe output and capture exit status in Bash
There is an internal Bash variable called $PIPESTATUS
; it’s an array that holds the exit status of each command in your last foreground pipeline of commands.
<command> | tee out.txt ; test ${PIPESTATUS[0]} -eq 0
Or another alternative which also works with other shells (like zsh) would be to enable pipefail:
set -o pipefail
...
The first option does not work with zsh
due to a little bit different syntax.
How to replace ${} placeholders in a text file?
Update
Here is a solution from yottatsa on a similar question that only does replacement for variables like $VAR or ${VAR}, and is a brief one-liner
i=32 word=foo envsubst < template.txt
Of course if i and word are in your environment, then it is just
envsubst < template.txt
On my Mac it looks like it was installed as part of gettext and from MacGPG2
Old Answer
Here is an improvement to the solution from mogsie on a similar question, my solution does not require you to escale double quotes, mogsie's does, but his is a one liner!
eval "cat <<EOF
$(<template.txt)
EOF
" 2> /dev/null
The power on these two solutions is that you only get a few types of shell expansions that don't occur normally $((...)), `...`, and $(...), though backslash is an escape character here, but you don't have to worry that the parsing has a bug, and it does multiple lines just fine.
Echo newline in Bash prints literal \n
Use printf
instead:
printf "hello\nworld\n"
printf
behaves more consistently across different environments than echo
.
Related Topics
Pid File Exists, But Process Is Not Running
"Unknown Symbol in Module" on Module Insertion Despite Export_Symbol
Visual Studio 2017 Could Not Create Directories, Mkdir Exit Code: 1
Docker: Permission Denied to Local MySQL Volume
Why Do Es and Ds Zero Out Eventually on 64 Bit Kernel When Set to Tls Selectors
Conversion from Ebcdic to Utf8 in Linux
Lapack/Blas/Openblas Proper Installation from Source - Replace System Libraries with New Ones
Where in The Linux Source Does Recognition of Specific Usb Devices Happen
How to Modify The Linux Kernel to Change The Version String That Uname Returns
Weird Behavior When Prepending to a File with Cat and Tee
What's The Meaning of This Sed Command? Sed 's%^.*/%%'
Bash Script with User Defined Functions in Autocomplete
Reliable Bidirectional Communication to a Linux Process
Accessing Apache2 Residing Inside Docker Container from Host Machine Web Browser