1

I have a file called data that looks like this:

orange
apple
pair
mango
grape

by using awk -i inplace '{if (NR==4) $0=$0" is sweet"}{print}' data, I get:

orange
apple
pair
mango is sweet
grape

I'm trying to achieve the same result through system() but I can't figure out how.

echo is sweet | xclip

xclip -o outputs is sweet. I'm trying

awk -i inplace '{if (NR==4) $0=$0(system("xclip -o"))}{print}' data

and cat -n data gives me

     1  orange
     2  apple
     3  pair
     4  is sweet
     5  mango0
     6  grape

Mango and grape have been pushed down one line, is sweet is on its own on line 4 and mango has a 0 in the end.

How can I change the command, and still use system("xclip -o") so that line 4 says mango is sweet, no appended 0 anywhere and no extra line, i.e. as with the first example?

I've super new with awk, and any help would be greatly appreciated.

1 Answers1

1

system("foo") just calls a program foo and returns foos exit status (i.e. 0 for success, non-zero for failure). You want something that will capture the output of foo (i.e. the text that foo writes to stdout), not it's exit status, so calling system() is the wrong approach. What you're seeing in your question is the output of xclip -o mixed in with the output of your awk script.

This is how to do what you want (using date instead of xclip since it's a command everyone has available):

$ awk '
    NR==4 {
        cmd = "date +%F"
        $0 = $0 " " ( (cmd | getline line) > 0 ? line : "N/A" )
        close(cmd)
    }
    { print }
 ' file
orange
apple
pair
mango 2021-09-06
grape

See https://www.gnu.org/software/gawk/manual/gawk.html#Getline_002fVariable_002fPipe for more information on the above and make sure to read http://awk.freeshell.org/AllAboutGetline to understand why I'm testing getline's exit status before using it's output and why it's not something to be used in many situations (but this one is appropriate).

As a "one-liner" the above would be:

awk 'NR==4 { cmd = "date +%F"; $0 = $0 " " ( (cmd | getline line) > 0 ? line : "N/A" ); close(cmd) } { print }' file

In case it's useful, here's a function that would do the equivalent for awk as command substitution for shell:

$ cat cmdsub.awk
function cmdsub(cmd,    line, n, out, cmd_stat, gl_stat) {
    ERRNO = ""
    while ( ( gl_stat=(cmd | getline line) ) > 0 ) {
        out = (n++ ? out ORS : "") line
    }
    cmd_stat = close(cmd)

    # If the above pipeline loop succeeded then all 3 error/status
    # variables will be 0 but if the above pipeline loop failed then
    #
    #    a) if cmd failed then
    #           i) ERRNO will be null
    #          ii) gl_stat will be 0 if 1st iteration, 1 otherwise.
    #         iii) cmd_stat will be non-zero if GNU awk,
    #                   poorly defined for POSIX awk so YMMV.
    #
    #    b) if cmd succeeded and getline failed then
    #           i) ERRNO will be non-null if GNU awk, null otherwise.
    #          ii) gl_stat will be less than zero
    #         iii) cmd_stat will be 0

    if ( (ERRNO == "") && ( (gl_stat != 0) || (cmd_stat != 0) ) ) {
        ERRNO = sprintf("CMD_STAT: %d, GL_STAT: %d", cmd_stat, gl_stat)
    }

    return out
}
BEGIN {
    print "foo", cmdsub("date +%F"), "bar"
    print (ERRNO ? "Failure: " ERRNO : "Success")
    exit (ERRNO ? 1 : 0)
}

$ awk -f cmdsub.awk
foo 2021-09-06 bar
Success

$ echo $?
0

It uses a global variable named ERRNO, the same as gawk already uses for getline failures (note: despite it's name, ERRNO is a string, not a number), and relies on close(cmd) returning a failure exit status when cmd fails which is what happens in gawk but in other awks YMMV as the POSIX standard is vague on that point.

For more info on ERRNO and close() see:

Ed Morton
  • 188,023
  • 17
  • 78
  • 185
  • I am trying your suggested command on my Ubuntu latest lts, Ed, and throws an error: `awk: cmd. line:1: NR==4 {cmd="date +%F" $0=$0 " " ((cmd | getline line) > 0 ? line : "N/A") close(cmd)}{print} awk: cmd. line:1: ^ syntax error` – Alexander Sofianos Sep 07 '21 at 10:23
  • There's nothing in the script I posted that would cause a syntax error in any awk so either you copy/pasted wrong or you're calling it from within a larger shell script and THAT has a problem. If it's the latter try copy/pasting it into http://shellcheck.net to see if it can help. Otherwise copy/paste exactly the command you're executing into your question (not a comment where it can't be formatted properly) so we can see what that command is. – Ed Morton Sep 07 '21 at 12:27
  • You're right Ed, instead of copying/pasting, I wrote the command in one line and probably got some spaces wrong, as I often do! Thanks a lot for your solution. It works just fine!! – Alexander Sofianos Sep 08 '21 at 03:36
  • Actually what I cannot understand Ed, is trying to write your command in one line it throws an error specifically if between `"date +%F"` and `$0 = $0` there is no change of line. – Alexander Sofianos Sep 08 '21 at 04:24
  • Basically, lines that don't end in `{` or `}` can either end in newlines or `;`s so if you're going to squeeze a multi-line script onto 1 line you need to add `;` at the end of those lines that don't have a `{` or `}` at the end to replace the newline rather than just deleting it. I updated my answer to show the one-line version. – Ed Morton Sep 08 '21 at 13:19
  • 1
    Thanks a lot for taking the time to clarify every little detail. I really appreciate it! – Alexander Sofianos Sep 09 '21 at 05:38