4

First of all I'm new to awk so please excuse if it's something simple.

I'm trying to generate a file that contains paths. I'm using for this an ls -LT listing as well as an awk script:

This is an example of the input file:

vagrant@precise64:/vagrant$ cat structure-of-home.cnf

/home/:

vagrant

/home/vagrant:

postinstall.sh

This would be the expected output:

/home/vagrant
/home/vagrant/postinstall.sh

The awk script should do the following:

  1. Check whether the line has a : in it
  2. If yes allocate the string (without :) to a variable ($path in my case)
  3. If the line is empty print nothing
  4. If it's not empty and it does not contain a : print the $path and then the current line $0

Here's the script:

BEGIN{
path=""
}
{
    if ($1 ~ /\:/)
        {
        sub(/\:/,"",$1)
        if (substr($1, length,1) ~ /\//)
            {
            path=$1;
            }
        else
            {
            path=$1"/"
            }
        }
    else if (length($0) == 0)
        {}
    else
        print $path$1
}

The problem is that when I run the script I get the following mess:

vagrant@precise64:/vagrant$ awk -f format_output.awk structure-of-home.cnf
vagrantvagrant
postinstall.shpostinstall.sh

What am I doing wrong please?

  • 2
    You are pritning $path, this is error, just do: print path $1; (path without $ in front of it) – taliezin Apr 18 '15 at 19:58
  • It is simple, but it's an easy mistake to make.  Note that, if path were equal to 3, then $path would be $3. – Scott Apr 19 '15 at 4:58
  • What system are you on? My ls (GNU coreutils 8.21) doesn't support ls -LT since -L is --dereference & -T is --tabsize requiering a cols argument. – mschilli Apr 30 '15 at 9:14
5

As pointed out by taliezin, your mistake was to use $ to expand path when printing. Unlike bash or make, awk doesn't use the $ to expand variables names to their value, but to refer to the fields of a line (similar to perl).

So just removing this will make your code work:

BEGIN{
path=""
}
{
    if ($1 ~ /\:/)
        {
        sub(/\:/,"",$1)
        if (substr($1, length,1) ~ /\//)
            {
            path=$1;
            }
        else
            {
            path=$1"/"
            }
        }
    else if (length($0) == 0)
        {}
    else
        print path$1
}

However, this is not really an awkish solution: First of all, there is no need to initialize path in a BEGIN rule, non-defined variables default to "" or 0, depending on context.

Also, any awk script consist of patterns and actions, the former stating when, the latter what to do. You have one action that's always executed (empty pattern), and internally uses (nested) conditionals to decide what to do.

My solution would look like this:

# BEGIN is actually a pattern making the following rule run only once:
# That is, before any input is read.
BEGIN{
  # Split lines into chunks (fields) separated by ":".
  # This is done by setting the field separator (FS) variable accordingly:
# FS=":"  # this would split lines into fields by ":"

  # Additionally, if a field ends with "/",
  # we consider this part of the separator.
  # So fields should be split by a ":" that *might*
  # be predecessed by a "/".
  # This can be done using a regular expression (RE) FS:
  FS="/?:"  # "?" means "the previous character may occur 0 or 1 times"

  # When printing, we want to join the parts of the paths by "/".
  # That's the sole purpose of the output field separator (OFS) variable:
  OFS="/"
}

# First we want to identify records (i.e. in this [default] case: lines),
# that contain(ed) a ":".
# We can do that without any RE matching, since records are
# automatically split into fields separated by ":".
# So asking >>Does the current line contain a ":"?<< is now the same
# as asking >>Does the current record have more than 1 field?<<.
# Luckily (but not surprisingly), the number of fields (NF) variable
# keeps track of this:
NF>1{  # The follwoing action is run only if are >1 fields.

  # All we want to do in this case, is store everything up to the first ":",
  # without the potential final "/".
  # With our FS choice (see above), that's exactly the 1st field:
  path=$1
}

# The printing should be done only for non-empty lines not containing ":".
# In our case, that translates to a record that has neither 0 nor >1 fields:
NF==1{  # The following action is only run if there is exactly 1 field.

  # In this case, we want to print the path varible (no need for a "$" here)
  # followed by the current line, separated by a "/".
  # Since we defined the proper OFS, we can use "," to join output fields:
  print path,$1  # ($1==$0 since NF==1)
}

And that's all. Removing all the comments, shortening the variable name and moving the [O]FS definitions to command line arguments, all you have to write is:

awk -F'/?:' -vOFS=\/ 'NF>1{p=$1}NF==1{print p,$1}' structure-of-home.cnf
1
awk -F: '/:/{prefix=$1;next}/./{print prefix "/" $0}'

Note that is not a problem to have double / in path.
But if you'd you can add

awk -F: '/:/{sub("/$","",$1);prefix=$1;next}/./{print prefix "/" $0}'

or

awk -F: '/:/{prefix=$1;s="/";if(prefix~"/$")s="";next}/./{print prefix s $0}'
  • 1
    This is not an answer to the question.  The question is not “How do I produce the desired output?”; the question is “What is wrong with my attempt at a solution?”  Yes, your answer fixes his problem, but your suggestions are so different from the OP’s that the absence of the $ is very hard to perceive.  And you didn’t explain any of it! – Scott Apr 19 '15 at 4:56
1

I'd do something like:

awk 'match($0, "/*:$") {path = substr($0, 1, RSTART-1); next}
     NF {print path "/" $0}'

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.