9

Is it possible to use gawk's -i inplace option and also print things to stdout?

For example, if I wanted to update a file, and if there are any changes print the name of the file and the changed lines to stderr I could do something like

find -type f -name 'myfiles' -exec gawk -i inplace '{if(gsub(/pat/, "repl")) { print FILENAME > "/proc/self/fd/2" ; print > "/proc/self/fd/2"; } print;}' {} +

but is there a way to use stdout instead, or a cleaner way to print that block to the alternate stream?

  • 2
    In this particular case you might want to run something like find -type f -name 'myfiles' -exec grep -q 'pattern' {} \; -print -exec gawk -i inplace '{do_your_sub_and_print_to_dev/stderr_too}' {} \; that way you will only use awk to edit the files that actually contain lines matching that pattern. – don_crissti Nov 8 '16 at 1:14
  • @don_crissti I often forget how much quicker grep can be. My first instinct is to avoid having to "process" the file twice, but I wouldn't be at all surprised if it is indeed faster nonetheless. It just goes to show why I can trust my gut on profiling tasks – Eric Renouf Nov 8 '16 at 1:19
  • 1
    Yes, it's quite fast and keep in mind that: 1) if there's no match, you only process the file once (the part with -print -exec gawk is not executed anymore) and 2) it will stop at 1st match so unless the 1st match is on the last line you're still not processing the whole file twice (it's more like 1.X times) . Also, if gawk -i inplace works like sed -i it will edit the file in-place anyway - i.e. updating timestamps, inode etc even if there was nothing to edit... – don_crissti Nov 8 '16 at 1:27
13

You should use /dev/stderr or /dev/fd/2 instead of /proc/self/fd/2. gawk handles /dev/fd/x and /dev/stderr by itself (regardless of whether the system has those files or not).

When you do a:

print "x" > "/dev/fd/2"

gawk does a write(2, "x\n"), while when you do:

print "x" > "/proc/self/fd/2"

since it doesn't treat /proc/self/fd/x specially, it does a:

fd = open("/proc/self/fd/2", O_WRONLY|O_CREAT|O_TRUNC);
write(fd, "x\n");

First /proc/self/fd is Linux specific and on Linux they are problematic. The two versions above are not equivalent when stderr is to a regular or other seekable file or to a socket (for which the latter would fail) (not to mention that it wastes a file descriptor).

That being said, if you need to write to the original stdout, you need to save it away in another fd like:

gawk -i inplace '{
   print "goes into the-file"
   print "to stdout" > "/dev/fd/3"}' the-file 3>&1

gawk does redirect stdout with in-place to the file. It's needed because for instance, you'd want:

awk -i inplace '{system("uname")}' file

to store the uname output into the file.

1

Just for fun, an alternate approach using only POSIX features of find, sh, grep and (most notably) ex:

find . -type f -name 'myfiles' -exec sh -c '
  for f do grep -q "pat" "$f" &&
    { printf "%s\n" "$f";
      printf "%s\n" "%s/pat/repl/g" x | ex "$f";
  }; done' find-sh {} +

(All line breaks in the above command are optional; it can be condensed to a one-liner.)

Or, arguably more legibly:

find . -type f -name 'myfiles' -exec sh -c '
  for f
  do
    if grep -q "pat" "$f"; then
      printf "%s\n" "$f"
      printf "%s\n" "%s/pat/repl/g" x | ex "$f"
    fi
  done' find-sh {} +

:)


(This command is untested; edits welcome if I made any goofs.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.