Como posso generalizar um comando awk em um script? (extraindo / rearranjando colunas do arquivo)

0

Estou tentando generalizar:

$ awk -F":" '{ print $7 ":" $1 }' /etc/passwd

em um script, com delimitador, arquivo de entrada e seleção de colunas fornecidas a partir de argumentos de linha de comando, algo como:

#! /bin/bash
# parse command line arguments into variables 'delimiter', 'cols' and 'inputfile'
...    

awk -F"$delimiter" '{ print '"$cols"' }' "$inputfile"

A entrada é de um arquivo, de modo que a entrada STDIN também pode ser aplicada. Eu preferiria especificar as colunas como argumentos separados em uma ordem. Os delimitadores de saída são os mesmos que os delimitadores de entrada, como no comando de exemplo.

Como você escreveria esse roteiro?

    
por Tim 22.07.2018 / 19:33

3 respostas

2

Você pode usar getopts do bash (você precisa desloque-se um pouco para baixo para fazer uma análise da linha de comando:

#!/bin/bash
delimiter=:
first=1
second=2
while getopts d:f:s: FLAG; do
  case $FLAG in
    d) delimiter=$OPTARG;;
    f) first=$OPTARG;;
    s) second=$OPTARG;;
    *) echo error >&2; exit 2;;
  esac
done
shift $((OPTIND-1))
awk -F"$delimiter" -v "OFS=$delimiter" -v first="$first" -v second="$second" '{ print $first OFS $second }' "$@"
    
por 22.07.2018 / 21:20
2

O script de shell a seguir usa uma opção opcional -d para definir o delimitador (a guia é padrão), bem como uma opção -c não opcional com uma especificação de coluna.

A especificação da coluna é semelhante à de cut , mas também permite reorganizar e duplicar as colunas de saída, além de especificar as faixas ao contrário. Os intervalos abertos também são suportados.

O arquivo a ser analisado é dado na linha de comando como o último operando ou passado na entrada padrão.

#!/bin/sh

delim='\t'   # tab is default delimiter

# parse command line option
while getopts 'd:c:' opt; do
    case $opt in
        d)
            delim=$OPTARG
            ;;
        c)
            cols=$OPTARG
            ;;
        *)
            echo 'Error in command line parsing' >&2
            exit 1
    esac
done
shift "$(( OPTIND - 1 ))"

if [ -z "$cols" ]; then
    echo 'Missing column specification (the -c option)' >&2
    exit 1
fi

# ${1:--} will expand to the filename or to "-" if $1 is empty or unset
cat "${1:--}" |
awk -F "$delim" -v cols="$cols" '
    BEGIN {
        # output delim will be same as input delim
        OFS = FS

        # get array of column specs
        ncolspec = split(cols, colspec, ",")
    }

    {
        # get fields of current line
        # (need this as we are rewriting $0 below)
        split($0, fields, FS)

        nf = NF     # save NF in case we have an open-ended range
        $0 = "";    # empty $0

        # go through given column specification and
        # create a record from it
        for (i = 1; i <= ncolspec; ++i)
            if (split(colspec[i], r, "-") == 1)
                # single column spec
                $(NF+1) = fields[colspec[i]]
            else {
                # column range spec

                if (r[1] == "") r[1] = 1    # open start range
                if (r[2] == "") r[2] = nf   # open end range

                if (r[1] < r[2])
                    # forward range
                    for (j = r[1]; j <= r[2]; ++j)
                        $(NF + 1) = fields[j]
                else
                    # backward range
                    for (j = r[1]; j >= r[2]; --j)
                        $(NF + 1) = fields[j]
            }

        print
    }'

Há uma pequena ineficiência nisso, pois o código precisa analisar novamente a especificação da coluna para cada nova linha. Se o suporte para intervalos abertos não for necessário, ou se todas as linhas tiverem exatamente o mesmo número de colunas, somente uma única passagem sobre a especificação poderá ser feita no bloco BEGIN (ou em uma separação NR==1 block) para criar uma matriz de campos que devem ser gerados.

Ausente: verificação de integridade para a especificação da coluna. Uma sequência de especificação malformada pode causar estranheza.

Teste:

$ cat file
1:2:3
a:b:c
@:(:)
$ sh script.sh -d : -c 1,3 <file
1:3
a:c
@:)
$ sh script.sh -d : -c 3,1 <file
3:1
c:a
):@
$ sh script.sh -d : -c 3-1,1,1-3 <file
3:2:1:1:1:2:3
c:b:a:a:a:b:c
):(:@:@:@:(:)
$ sh script.sh -d : -c 1-,3 <file
1:2:3:3
a:b:c:c
@:(:):)
    
por 23.07.2018 / 09:45
0

Obrigado por respostas. Aqui está meu roteiro. Eu criei por tentativa e erro, o que muitas vezes não leva a uma solução de trabalho, e não tenho uma maneira sistemática de criar um script que eu sempre viso. Por favor, forneça uma revisão de código, se puder. Obrigado.

O script funciona nos seguintes exemplos (não tenho certeza se funciona em geral):

$ projection -d ":" /etc/passwd 4 3 6 7

$ projection -d "/" /etc/passwd 4 3 6 7

Script projection é:

#! /bin/bash

# default arg value                                                                                                                                                               
delim="," # CSV by default                                                                                                                                                        
# Parse flagged arguments:                                                                                                                                                        
while getopts "td:" flag
do
  case $flag in
    d) delim=$OPTARG;;
    t) delim="\t";;
    ?) exit;;
  esac
done
# Delete the flagged arguments:                                                                                                                                                   
shift $(($OPTIND -1))

inputfile="$1"
shift 1

fs=("$@")
# prepend "$" to each field number                                                                                                                                                
fields=()
for f in "${fs[@]}"; do
    fields+=(\$"$f")
done

awk -F"$delim" "{ print $(join_by.sh " \"$delim\" " "${fields[@]}") }" "$inputfile"

em que join_by.sh é

#! /bin/bash                                                                                                                                                                      

# https://stackoverflow.com/questions/1527049/join-elements-of-an-array                                                                                                           
# https://stackoverflow.com/a/2317171/                                                                                                                                

# get the separator:                                                                                                                                                              
d="$1";
shift;

# interpolate other parameters by teh separator                                                                                                                                   
# by treating the first parameter specially                                                                                                                                       
echo -n "$1";
shift;
printf "%s" "${@/#/$d}";
    
por 23.07.2018 / 15:19