Reputation: 3739
I have a config file which contains some ENV_VARIABLE styled variables.
This is my file.
It might contain $EXAMPLES of text.
Now I want that variable replaced with a value which is saved in my actual environment variables. So I'm trying this:
export EXAMPLES=lots
envsubst < file.txt > file.txt
But it doesn't work when the input file and output file are identical. The result is an empty file of size 0.
There must be a good reason for this, some bash basics that I'm not aware of? How do I achieve what I want to do, ideally without first outputting to a different file and then replacing the original file with it?
I know that I can do it easily enough with sed
, but when I discovered the envsubst
command I thought that it should be perfect for my use case, so I'd like to use that.
Upvotes: 51
Views: 45297
Reputation: 31
This works just fine without the need for temp file. Expression is evaluated before it flushes the content to the file.
echo "$(envsubst < file.txt)" > file.txt
Upvotes: 3
Reputation: 145
envsubst < file.txt | tee file.txt
Caution: @SoftwareFactor rightly pointed out in the comments that this answer suffers from a race condition and can cause the resulting file to be empty at random.
Upvotes: 4
Reputation: 151
I found another shortcut to put into temp file and then rename it to original file.
envsubst < in.txt > out.txt && mv out.txt in.txt
Upvotes: 15
Reputation: 751
To avoid creating a temporary file, use sponge
not tee
:
envsubst < file.txt | sponge file.txt
From https://linux.die.net/man/1/sponge:
sponge reads standard input and writes it out to the specified file. Unlike a shell redirect, sponge soaks up all its input before opening the output file. This allows constricting pipelines that read from and write to the same file.
Upvotes: 29
Reputation: 873
If you use bash, check this:
a=`<file.txt` && envsubst <<<"$a" >file.txt
Tested on 500mb file, works as expected.
Upvotes: 0
Reputation: 809
Updated 20221011 - Using 1 sed command
sed -i -r 's/["`]|\$\(/\\&/g; s/.*/echo "&"/ e' ./input.txt
Updated 20221007 - Using 2 sed commands
sed -i -r 's/["`]|\$\(/\\&/g' input.txt
sed -i -r 's/.*/echo "&"/ e' input.txt
Do it without envsubst
envsubst_file () {
local original_file=$1
local temp_file=$(mktemp)
trap "rm -f ${temp_file}" 0 2 3 15
cp -p ${original_file} ${temp_file}
cat ${original_file} | sed -r 's/["`]|\$\(/\\&/g' | sed -r 's/.*/echo "&"/g' | sh > ${temp_file}
mv ${temp_file} ${original_file}
}
envsubst_file 'input.txt'
First using sed to escapes double quotes("), backtick(`) and command $( by prefixing with backslash(\),then using sed again replace with
echo "&"
Finally executing the shell script and redirecting to ${temp_file}
Upvotes: 2
Reputation: 8588
Here is the solution that I use:
originalfile="file.txt"
tmpfile=$(mktemp)
cp --attributes-only --preserve $originalfile $tmpfile
cat $originalfile | envsubst > $tmpfile && mv $tmpfile $originalfile
Be careful with other solutions that do not use a temporary file. Pipes are asynchronous, so the file will occasionally be read after it has already been truncated.
Upvotes: 31
Reputation: 317
This answer was framed from two other answers. I guess this is the best solution.
originalFile=file.txt
tmpfile=$(mktemp)
cat $originalFile | envsubst > "$tmpfile" && cp -pf "$tmpfile" $originalFile
rm -f "$tmpfile"
Upvotes: 0
Reputation: 241701
Redirects are handled by the shell, not the program being executed, and they are set up before the program is invoked.
The redirect >output.file
has the effect of creating output.file
if it doesn't exist and emptying it if it does. Either way, you end up with an empty file, and that is what the program's output is redirected to.
Programs like sed
which are capable of "in-place" modification must take the filename as a command-line argument, not as a redirect.
In your case, I would suggest using a temporary file and then renaming it if all goes OK.
Upvotes: 16
Reputation: 81
It's worth noting that the mv
solution won't maintain file permissions. Using cp -pf
would be preferable in the case that you're modifying an executable file.
tmpfile=$(mktemp)
cat file.txt | envsubst > "$tmpfile" && cp -pf "$tmpfile" file.txt
rm -f "$tmpfile"
Upvotes: 1
Reputation: 3739
In the end I found that using envsubst
was too dangerous after all. My files might contain dollar signs in places where I don't want any substitution to happen, and envsubst
will just replace them with empty strings if no corresponding environment variable is defined. Not cool.
Upvotes: -4
Reputation: 1274
You can achieve in-place substitution by calling envsubst from gnu sed with the "e" command:
EXAMPLES=lots sed -i 's/.*/echo & | envsubst/e' file.txt
Upvotes: 1