Reputation: 97
Within an AWK script, I'm needing to send the output of the script to a file while also printing it to the terminal. Is there a nice and tidy way I can do this without having a copy of every print redirect to the file?
I'm not particularly good at making SSCCE examples but here's my attempt at demonstrating my problem;
BEGIN{
print "This is an awk script"
# I don't want to have to do this for every print
print "This is an awk script" > thisiswhack.out
}
{
# data manip. stuff here
# ...
print "%s %s %s" blah, blah blah
# I don't want to have to do this for every print again
print "%s %s %s" blah blah blah >> thisiswhack.out
}
END{
print "Yay we're done!"
# Seriously, there has to be a better way to do this within the script
print "Yay we're done!" >> thisiswhack.out
}
Surely there must be a way to send the entire output of the script to an output file within the script itself, right?
Upvotes: 1
Views: 2169
Reputation: 203665
awk '
function prtf(str) {
printf "%s", str > "thisiswhack.out"
printf "%s", str
fflush()
}
function prt(str) {
prtf( str ORS )
}
{
# to print adding a newline at the end:
prt( "foo" )
# to print as-is without adding a newline:
prtf( sprintf("%s, %s, %d", $2, "bar", 17) )
}
' file
In the above we are not spawning a subshell to call any other command so it's efficient, and we're using fflush()
after every print to ensure both output streams (stdout and the extra file) don't get out of sync with respect to each other (e.g. stdout displays less text than the file or vice-versa if the command is killed).
The above always overwrites the contents of "thisiswhack.out" with whatever the script outputs. If you want to append instead then change >
to >>
. If you want the option of doing both, introduce a variable (which I've named prtappend
below) to control it which you can set on the command line, e.g. change:
printf "%s", str > "thisiswhack.out"
to:
printf "%s", str >> "thisiswhack.out"
and add:
BEGIN {
if ( !prtappend ) {
printf "" > "thisiswhack.out"
}
}
then if you do awk -v prtappend=1 '...'
it'll append to thisiswhack.out
instead of overwriting it.
Of course, the better approach if you're on a Unix system is to have your awk script called from a shell script with it's output piped to tee
, e.g.:
#!/usr/bin/env bash
awk '
{
print "foo"
printf"%s, %s, %d", $2, "bar", 17
}
' "${@:--}" |
tee 'thisiswhack.out'
Note that this is one more example of why you should not call awk from a shebang.
Upvotes: 2
Reputation: 5231
The command to duplicate streams is tee
, and we can use it inside awk:
awk '
BEGIN {tee = "tee out.txt"}
{print | tee}' in.txt
This invokes tee
with the file argument out.txt
, and opens a stream to this command.
The stream (and therefore tee
) remains open until awk exits, or close(tee)
is called.
Every time print | tee
is used, the data is printed to that stream. tee
then appends this data both to the file out.txt
, and stdout.
The | command
feature is POSIX awk. Also the tee
variable isn't compulsory (you can use the string).
Of course, we can use tee outside awk too: awk ... | tee out.txt
.
Upvotes: 2
Reputation: 36520
GNU AWK
's Redirection allows sending output to command, rather than file, therefore I suggest following exploit of said feature:
awk 'BEGIN{command="tee output.txt"}{print tolower($0) | command}' input.txt
Note: I use tolower($0)
for demonstration purposes. I redirect print into tee
command, which does output to mentioned file and standard output, thus you should get lowercase version of input.txt
written to output.txt
and standard output.
If you are not confined to single awk
usage then you might alternatively use tee
outside, like so
awk '{print tolower($0)}' input.txt | tee output.txt
Upvotes: 1