awk ifs e variáveis - não pode passar uma variável de uma linha para linhas subsequentes

4

Primeiro de tudo, eu sou novo no awk, então, por favor, desculpe se é algo simples.

Estou tentando gerar um arquivo que contenha caminhos. Estou usando para isso uma listagem ls -LT e um script awk:

Este é um exemplo do arquivo de entrada:

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

/home/:

vagrant

/home/vagrant:

postinstall.sh

Esta seria a saída esperada:

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

O script awk deve fazer o seguinte:

  1. Verifique se a linha tem um :
  2. Se sim, aloque a string (sem : ) para uma variável ( $path no meu caso)
  3. Se a linha estiver vazia, não imprima nada
  4. Se não estiver vazio e não contiver : , imprima a $path e, em seguida, a linha atual $0

Aqui está o 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
}

O problema é que, quando executo o script, recebo a seguinte confusão:

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

O que estou fazendo de errado, por favor?

    
por tcsapunaru 18.04.2015 / 21:28

3 respostas

5

Como apontado por taliezin , seu erro foi usar $ para expandir path ao imprimir. Ao contrário de bash ou make , awk não usa o $ para expandir nomes de variáveis para seu valor, mas para se referir aos campos de uma linha (semelhante a perl ).

Então, apenas removendo isso, seu código funcionará:

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
}

No entanto, isso não é realmente uma solução awk ish: Primeiro de tudo, não há necessidade de inicializar path em uma regra de BEGIN , padrão de variáveis não definidas para "" ou 0 , dependendo do contexto.

Além disso, qualquer script awk consiste em padrões e ações , o primeiro informando quando , o último o que fazer. Você tem uma ação que é sempre executada (vazio padrão ) e internamente usa condicionais (aninhados) para decidir o que fazer.

Minha solução ficaria assim:

# 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)
}

E isso é tudo. Removendo todos os comentários, encurtando o nome da variável e movendo as definições [O]FS para os argumentos da linha de comando, tudo o que você precisa escrever é:

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

Observe que não é um problema ter duplo / no caminho.
Mas se você pode adicionar

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

ou

awk -F: '/:/{prefix=$1;s="/";if(prefix~"/$")s="";next}/./{print prefix s $0}'
    
por 18.04.2015 / 22:14
1

Eu faria algo como:

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

Tags