1

I have a command that prints the line directly after a match with several pipes using different tools like grep, awk, and tail. This works but it feels somewhat messy and uses multiple pipes to complete this task. Is it possible to do this in one command using just one of these tools? There is also a line of whitespace at the end of the file.

File contents:

10:28:48  2022-09-15T15:28:36Z:    Creating tar ball for build artifacts
--
10:28:53  --> Build complete: Artifacts were created:
file: build-1.tar.gz

Command

grep -A1 "Artifacts were created:" $file | tail -n1 | awk '{print $2}'

Result

build-1.tar.gz

This technically works but it seems kind of sloppy.

1
  • Fixed the requirement to match the code.
    – AndG
    Sep 27, 2022 at 18:20

5 Answers 5

2

Please try:

awk '/Artifacts were created:/{p=1} NF{out=$2}  END{if(p==1){print out}}' infile

Although the pipe you presented is extracting the second filed of the line just following the match, not the last one. That could be converted to this one call to awk:

awk '
      # Have we found the match?
      /Artifacts were created:/{ found=1 ; next }
      
      # if a match has been found, print and exit
      found == 1 { print $2; exit }

    ' "$file"

0
2

If sed is an option

$ sed -En '/Artifacts were created:/{n;s/[^ ]* (.*)/\1/p}' <<< "$file"
build-1.tar.gz
2

The original requirement asked for a value from the last non-whitespace line after a match. Here, when you find a match trigger, set a flag. When the flag is set wait for the end of file and then print the last item we captured:

awk '
    # Set the flag on match
    /Artifacts were created/ { flag++ }

    # Capture second field of a line that is not just whitespace
    NF { lastitem = $2 }

    # At the end, maybe print the last captured item
    END { if (flag) { print lastitem } }
' "$file"

In some versions of awk you might be able to reference the last $2 inside the END clause. Here I prefer a more direct approach by capturing the most recent line item as we go through the file.


The updated requirement asks for a value from the line immediately following a match. This is far simpler, and potentially faster since the file does not necessarily need to be processed in its entirety:

awk '
    # Output second field of line immediately following the flag being set
    flag && NF { print $2; exit }

    # Set the flag on match
    /Artifacts were created/ { flag++ }

' "$file"
0
0

This:

awk '{if (match) {print $2; exit}; if (/Artifacts were created/) {match=1}}' <<EOF
10:28:48  2022-09-15T15:28:36Z:    Creating tar ball for build artifacts
--
10:28:53  --> Build complete: Artifacts were created:
file: build-1.tar.gz
EOF

seems to produce exactly what you want:

build-1.tar.gz
-1

Using Raku (formerly known as Perl_6)

~$ raku -e 'put $<>.words[1] if m{ "Artifacts were created:" \n <( \N* } for lines.join: "\n";'  file

#OR

~$ raku -e 'put $0.words[1] if m{ "Artifacts were created:" \n (\N*) } for lines.join: "\n";'  file  

If the OP is willing to consider Perl-family Programming languages, Raku is a good choice. Above, the input file is read-in linewise, and joined with \n newlines. The target "Artifacts were created:" \n is identified, after which the match is sought.

In the first example, Raku's capture markers <( … )> are used to drop the "Artifacts..." line from the match object, and the .words[1] second whitespace-separated field of the capture is output using the $<> (or synonymous $/) match variable (subject to an if conditional). Actually, for this problem only one capture marker <( is needed.

In the second example, parentheses are used to capture a portion of the match into match-variable $<>.[0] which is the same as match-variable $/.[0] which is the same as $0. The .words[1] second whitespace-separated field of the $0 capture is output subject to an if conditional.

Sample Input:

10:28:48  2022-09-15T15:28:36Z:    Creating tar ball for build artifacts
--
10:28:53  --> Build complete: Artifacts were created:
file: build-1.tar.gz

Sample Output:

build-1.tar.gz

There really are quite a number of built-in character classes in Raku, so the characteristic [\N* \n] pattern (zero-or-more non-newline characters followed by a newline) can be replicated for many other patterns (e.g. [\S* \s] or [\d* \D], etc.).

https://docs.raku.org/language/regexes
https://raku.org

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .