awk 명령을 스크립트로 어떻게 요약할 수 있나요? (파일에서 열 추출/재배열)

awk 명령을 스크립트로 어떻게 요약할 수 있나요? (파일에서 열 추출/재배열)

나는 요약하려고 노력했다:

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

다음과 같은 명령줄 인수에서 제공되는 구분 기호, 입력 파일 및 열 선택 사항을 사용하여 스크립트로 변환합니다.

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

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

입력은 파일에서 나오므로 STDIN 입력도 작동합니다. 나는 열을 순서대로 별도의 매개변수로 지정하는 것을 선호합니다. 출력 구분 기호는 예제 명령에 표시된 대로 입력 구분 기호와 동일합니다.

이런 스크립트를 어떻게 작성하시겠어요?

답변1

당신은 그것을 사용할 수 있습니다배쉬의getopts(조금 아래로 스크롤해야 합니다.) 명령줄 구문 분석을 수행합니다.

#!/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 }' "$@"

답변2

다음 셸 스크립트는 -d구분 기호를 설정하는 선택적 옵션(탭이 기본값)과 -c열 사양이 있는 비선택적 옵션을 사용합니다.

열 사양은 와 유사 cut하지만 출력 열을 다시 정렬하고 복사할 수 있으며 범위를 거꾸로 지정할 수도 있습니다. 개방형 범위도 지원됩니다.

구문 분석할 파일은 명령줄의 마지막 피연산자로 제공되거나 표준 입력을 통해 전달됩니다.

#!/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
    }'

코드가 각각의 새 행에 대한 열 사양을 다시 구문 분석해야 하므로 이는 약간 덜 효율적입니다. 열린 범위를 지원할 필요가 없거나 모든 행에 정확히 동일한 수의 열이 있다고 가정하는 경우 배열이 출력해야 하는 필드를 생성하기 위해 블록 BEGIN(또는 별도의 블록)에서 하나의 사양 전달만 수행할 수 있습니다. NR==1.

누락: 열 사양에 대한 온전성 검사. 잘못된 사양 문자열로 인해 이상한 동작이 발생할 가능성이 높습니다.

시험:

$ 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
@:(:):)

답변3

당신의 답변에 감사드립니다. 이것은 내 스크립트입니다. 시행착오를 거쳐 만들었지만, 실행 가능한 솔루션이 나오지 않는 경우가 많았고, 제가 목표로 삼았던 스크립트를 체계적으로 생각해 낼 수 있는 방법도 없었습니다. 가능하다면 코드 리뷰를 제공해 주세요. 감사해요.

스크립트는 다음 예에서 작동합니다(일반적으로 작동하는지 확실하지 않음).

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

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

스크립트는 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"

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}";

관련 정보