awk - How to preserve whitespace?

Given a file:

# configuration file for newsyslog
# $FreeBSD: /repoman/r/ncvs/src/etc/newsyslog.conf,v 1.50 2005/03/02 00:40:55 brooks Exp $
#
# Entries which do not specify the '/pid_file' field will cause the
# syslogd process to be signalled when that log file is rotated.  This
# action is only appropriate for log files which are written to by the
# syslogd process (ie, files listed in /etc/syslog.conf).  If there
# is no process which needs to be signalled when a given log file is
# rotated, then the entry for that file should include the 'N' flag.
#
# The 'flags' field is one or more of the letters: BCGJNUWZ or a '-'.
#
# Note: some sites will want to select more restrictive protections than the
# defaults.  In particular, it may be desirable to switch many of the 644
# entries to 640 or 600.  For example, some sites will consider the
# contents of maillog, messages, and lpd-errs to be confidential.  In the
# future, these defaults may change to more conservative ones.
#
# logfilename          [owner:group]    mode count size when  flags [/pid_file] [sig_num]
/var/log/appfirewall.log		640  5     1000	*     J
/var/log/ftp.log			640  5	   1000	*     J
/var/log/hwmond.log			640  5	   1000	*     J
/var/log/install.log			640  5	   1000	*     J
/var/log/ipfw.log			640  5	   1000	*     J
/var/log/lookupd.log			640  5	   1000	*     J
/var/log/lpr.log			640  5	   1000	*     J
/var/log/mail.log			640  5	   1000	*     J
/var/log/ppp.log			640  5	   1000	*     J
/var/log/secure.log			640  5	   1000	*     J
/var/log/system.log			640  7	   *	@T00  J
/var/log/wtmp				644  3	   *	@01T05 B

I want to change the third, fourth, and fifth fields, so I wrote:

awk '/^\// {$3=13}{$4="*"}{$5="$W1"}{print}' newsyslog.conf 

However, that results in:

# configuration file * $W1
# $FreeBSD: /repoman/r/ncvs/src/etc/newsyslog.conf,v * $W1 00:40:55 brooks Exp $
#   * $W1
# Entries which * $W1 specify the '/pid_file' field will cause the
# syslogd process * $W1 signalled when that log file is rotated. This
# action is * $W1 for log files which are written to by the
# syslogd process * $W1 listed in /etc/syslog.conf). If there
# is no * $W1 needs to be signalled when a given log file is
# rotated, then * $W1 for that file should include the 'N' flag.
#   * $W1
# The 'flags' * $W1 one or more of the letters: BCGJNUWZ or a '-'.
#   * $W1
# Note: some * $W1 want to select more restrictive protections than the
# defaults. In * $W1 may be desirable to switch many of the 644
# entries to * $W1 600. For example, some sites will consider the
# contents of * $W1 and lpd-errs to be confidential. In the
# future, these * $W1 change to more conservative ones.
#   * $W1
# logfilename [owner:group] * $W1 size when flags [/pid_file] [sig_num]
/var/log/appfirewall.log 640 13 * $W1 J
/var/log/ftp.log 640 13 * $W1 J
/var/log/hwmond.log 640 13 * $W1 J
/var/log/install.log 640 13 * $W1 J
/var/log/ipfw.log 640 13 * $W1 J
/var/log/lookupd.log 640 13 * $W1 J
/var/log/lpr.log 640 13 * $W1 J
/var/log/mail.log 640 13 * $W1 J
/var/log/ppp.log 640 13 * $W1 J
/var/log/secure.log 640 13 * $W1 J
/var/log/system.log 640 13 * $W1 J
/var/log/wtmp 644 13 * $W1 B

And I just realized it was acting on every line, not lines starting with a '/'

So, I now have two questions... one is, how do I preserve the original whitespace; two, how to I get multiple substitutions limited to those lines that match my pattern?

How about:

awk '/^[/]/ {
    L=$0
    s=index(L, $3)
    L=substr(L,0,s-1) "13" substr(L,s+length($3));
    s=index(L, $4)
    L=substr(L,0,s-1) "*" substr(L,s+length($4));
    s=index(L, $5)
    $0=substr(L,0,s-1) "$W1" substr(L,s+length($5));
} 1' newsyslog.conf
awk: nonterminated character class ^[
 source line number 1
 context is
	 >>> /^[/ <<<

Didn't like that either... :slight_smile:

Removing the brackets and escaping the forward slash got me to where you wound up.

With GNU awk >= 4 you could try something like this:

awk '/^\// {
  n = split($0, t, FS, s)
  t[3] = 13
  t[4] = "*"
  t[5] = "$W1"
  for (i = 0; ++i <= n;)
    printf "%s", t s
  print x
  next
  }1' infile

try also:

awk '
BEGIN {c[3]=13; c[4]="*"; c[5]="$W1"}
$0 ~ /^\// {
   l=$0; o="";
   for (i=1; i<=NF; i++) {
     w=l; sub("[ \t].*", "", w);
     o=o (c ? c : w);
     sub("^[ \t]*", "", l);
     sub("^[^ \t]*" , "", l);
     w=l; sub("[^ \t].*", "", w);
     o=o w; sub("^[ \t]*", "", l);
   }
   $0=o;
} 1
' newsyslog.conf
1 Like

I'm on a Mac, and have to use the BSD tools that come with OSX, as I could not count on GNU tools being available :frowning:

Still unhappy with my solution, think this works much better:

awk '
function rep(a,v) {
  sub("^(([^ \t]+[ \t]+){"a-1"})","&" SUBSEP);
  sub(SUBSEP "[^ \t]+", v);
}   
/^\// {
    rep(3,"13")
    rep(4,"*")
    rep(5,"$W1")
} 1' newsyslog.conf

This looks like it'll work!

The output is just a little "off", but I believe it'll work. Just in case there's a reasonable way to make the columns a little more aesthetic...

/var/log/appfirewall.log		640  13     *	$W1     J
/var/log/ftp.log			640  13	   *	$W1     J
/var/log/hwmond.log			640  13	   *	$W1     J
/var/log/install.log			640  13	   *	$W1     J
/var/log/ipfw.log			640  13	   *	$W1     J
/var/log/lookupd.log			640  13	   *	$W1     J
/var/log/lpr.log			640  13	   *	$W1     J
/var/log/mail.log			640  13	   *	$W1     J
/var/log/ppp.log			640  13	   *	$W1     J
/var/log/secure.log			640  13	   *	$W1     J
/var/log/system.log			640  13	   *	$W1  J
/var/log/wtmp				644  13	   *	$W1 B

This seems to come close to what you're expecting (without needing GNU awk >= 4):

awk '
/^\// { $3 = 13
        $4 = "*"
        $5 = "$W1"
        printf("%s", $1)
        for(w = length($1); w < 40; w += 8) printf("\t")
        printf("%-5d%-2d\t   %s\t%-6s%s\n", $2, $3, $4, $5, $6)
        next
}
1' input

If you're using a Solaris/SunOS system, use /usr/xpg4/bin/awk or nawk instead of awk.
It produces:

... ... ...
# contents of maillog, messages, and lpd-errs to be confidential.  In the
# future, these defaults may change to more conservative ones.
#
# logfilename          [owner:group]    mode count size when  flags [/pid_file] [sig_num]
/var/log/appfirewall.log		640  13	   *	$W1   J
/var/log/ftp.log			640  13	   *	$W1   J
/var/log/hwmond.log			640  13	   *	$W1   J
/var/log/install.log			640  13	   *	$W1   J
/var/log/ipfw.log			640  13	   *	$W1   J
/var/log/lookupd.log			640  13	   *	$W1   J
/var/log/lpr.log			640  13	   *	$W1   J
/var/log/mail.log			640  13	   *	$W1   J
/var/log/ppp.log			640  13	   *	$W1   J
/var/log/secure.log			640  13	   *	$W1   J
/var/log/system.log			640  13	   *	$W1   J
/var/log/wtmp				644  13	   *	$W1   B

except that the copy and paste used to produce this converts the tabs in the output to spaces.

I copied and pasted from the first example output and from the last output posted and the spacing looks fine.

try adding | expand after input file. This will fill in the white space.

Try this one as well:

awk '
function rep(a,v) {
  sub("^(([^ \t]+[ \t]+){"a-1"})","&" SUBSEP);
  sub(SUBSEP "[^ \t]+", v);
}   
/^\// {
    rep(3,"13")
    rep(4,"*")
    rep(5,"$W1")
} 1' newsyslog.conf