I am trying to capture a set of text from sudo
files across various Linux servers via a Bash script.
Some of the text is formatted as follows:
Cmnd_XXXX Alias = /path/to/command/ command,
/path/to/command/2 command,
/path/to/command/3 command,
/path/to/command/4 command,
/path/t/command/5 command
I am trying to figure out if there is a way to capture the entire list of commands and nothing else.
The closest I’ve come is using like grep -A 6
so it would capture that first line and the following 6 lines after, however the amount of commands tend to vary per server and file. So I am trying to figure out if there is way to tell Linux to grab everything after each of those backslashes up until the last command in the list.
Mogwuy is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
Here is a simple Awk script which prints lines which end with a backslash starting from the matching line.
awk -v search="Cmnd_" '
$0 ~ search { p=1 }
p
!/\$/ { p=0 }' file
We set p
to 1 if we find the search expression, then print lines as long as p
is nonzero. If a line doesn’t end with a backslash, we set p
back to zero (don’t print).
Demo: https://ideone.com/u9fXAc
This simple tool just ignores lines before the match, even if they would end with a backslash. It’s not much harder to collect all lines joined by backslash+newline into a single record, but this crude tool doesn’t do that.
I took a quick stab at it (using AWK only from your subject line), putting your text into a file (a couple of times) along with some other random text to filter out (representing the multiple sources in your question).
I threw the AWK script into a file for easy editing, but I’m sure it could be simplified into a one-liner.
cat ./example.txt | awk -f ./filter.awk
filter.awk:
BEGIN { p=0; }
{
if ( substr($1,1,5) == "Cmnd_" ) p=3;
if (substr($0,length($0),1)=="\") p=2;
if ( p > 0 )
{
if ( p == 2 ) s = substr($0,1,length($0)-1);
else s = $0;
printf "%s ",s;
--p;
if ( p == 0 ) printf "n";
}
}
I assumed you wanted to filter on “Cmnd_”, and added that.
I was able to capture the line ending “” and output those lines, and had it keep the final non-continued line (command 5 above). You can adjust to meet your specific need.
Cmnd_XXXX Alias = /path/to/command/ command, /path/to/command/2 command, /path/to/command/3 command, /path/to/command/4 command, /path/t/command/5 command
David King is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
3
Using GNU awk for multi-char RS
and RT
:
$ awk -v RS='[^\\]n' '/^Cmnd_/{printf "%s", $0 RT}' file
Cmnd_XXXX Alias = /path/to/command/ command,
/path/to/command/2 command,
/path/to/command/3 command,
/path/to/command/4 command,
/path/to/command/4 command,
/path/t/command/5 command
$ awk -v RS='[^\\]n' -F'\s*[\\]n' '/^Cmnd_/{$1=$1; printf "%s", $0 RT}' file
Cmnd_XXXX Alias = /path/to/command/ command, /path/to/command/2 command, /path/to/command/3 command, /path/to/command/4 command, /path/t/command/5 command
You can just use awk’s range pattern to print from the line matching /^Cmnd_/
till the first following line(including itself) which is not trailing with the backslash /[^\]$/
awk '/^Cmnd_/,/[^\]$/' file
To merge related command lines into one
awk -F, '/^Cmnd_/,/[^\]$/{ printf("%s%s", $1, NF>1?", ":RS) }' file
#Cmnd_XXXX Alias = /path/to/command/ command, /path/to/command/2 command, /path/to/command/3 command, /path/to/command/4 command, /path/t/command/5 command
This might work for you (GNU sed):
sed -n '/Cmnd_/{:a;$!{N;/\$/ba};p}' file
Find a line containing Cmnd_
and append the next line and if that line ends in go again.
If the appended line does not end in then print the collection.