쉘에서 문자열 조작

쉘에서 문자열 조작

저는 작업 중인 XML 중 하나에 대한 입력으로 사용할 수 있도록 특정 형식의 문자열을 생성하는 셸 스크립트를 작성하고 있습니다.

다음 형식의 입력 파일이 제공됩니다.<attribute field>,<data_type>,<size>

instanceid,varchar,256
sysdate,date
status,number
notes,varchar,4000
created_on,date

"체크섬"을 변수(예: md5( INSTANCEID || STATUS || NOTES)Or'd)에 있는 필드를 제외한 모든 속성 필드에 저장하고 싶습니다.

제가 쓴 스크립트는 이렇습니다

IFS=$'\n'
file=$(cat source.txt)
line_number=$(cat source.txt | wc -l)
checksum="md5( "
for line in $file
do
let line_number=line_number-1
data_field=$(echo $line | cut -f1 -d','| tr "a-z" "A-Z")
data_type=$(echo $line | cut -f2 -d',' | tr "a-z" "A-Z")
if [ $data_type != "DATE" ]  && [ $line_number -gt 0 ]
  then checksum+="$data_field || "
elif [ $data_type != "DATE" ] && [ $line_number -eq 0 ]
  then checksum+=" $data_field "
fi
done
checksum+=")"
echo $checksum

이 스크립트는 날짜 유형의 속성이 있는 마지막 행을 제외한 모든 입력 시나리오에서 작동합니다.

이 경우 변수의 값은 다음과 같습니다.md5( INSTANCEID || STATUS || NOTES || )

마지막 행이 날짜인지 확인하기 위해 명령을 사용해 보았지만 tail마지막 행이 날짜 유형이면 다시 실패합니다.

||마지막 항목을 어떻게 제거할 수 있나요 ?

답변1

checksum="${checksum% || })"대신 빠른 대답은 입니다 checksum+=")". 각 단계에서 무조건 문자열을 추가한 ||다음 마지막에 불필요한 마지막 문자열을 제거하면 됩니다(그러므로 line_number더 이상 계산이 필요하지 않습니다).

더 좋은 방법은

awk -F, 'BEGIN { printf "md5( " } 
         toupper($2) != "DATE" { printf "%s%s", sep, toupper($1); sep = " || " }
         END { print ")" }' source.txt

답변2

  • cat쉘 스크립트에서는 거의 유용하지 않다는 점은 주목할 가치가 있습니다. $(cat source.txt | wc -l)고전이다쓸모없는 사용cat; 이것은 파일의 줄 수를 계산해야 하는 경우 $(wc -l < source.txt)더 깔끔한 방법입니다 .
  • 그러나 계산에는 행 수가 필요하지 않습니다 source.txt.
  • file=$(cat source.txt)파일을 읽는 추악한 방법입니다.
    읽는 동안…
    하다
        ︙
    완료<파일 이름
    더 나은.  read이것의 이점은 행을 필드로 분할한다는 것입니다.
  • tr전체 파일에 대해 한 번만 실행하면 되지만 파일의 각 줄에 대해 두 번 실행하는 것은 어리석은 일입니다. 일부 경우에,
    트…<파일 이름|읽는 동안...
    하다
        ︙
    완벽한
    좋은 결과. 그러나 문제가 있습니다. while루프가 하위 쉘에서 실행되므로 checksum쉘 변수(예를 들어)에 대한 변경 사항은 루프가 끝난 후에 표시되지 않습니다. Terdon은 이 문제를 해결하는 한 가지 방법을 보여줍니다.
    트…<파일 이름|{읽는 동안…
    하다
            ︙
        변경될 수 있는 주문체크섬.
            ︙
    완벽한
        ︙
    사용된 명령$체크섬.
        ︙
    }
  • 아시다시피, 마지막으로 발생한 문제가 어디인지 파악하는 것은 어려울 수 있습니다. 일반적으로 첫 번째 항목을 식별하는 것이 더 쉽습니다.

    checksum="md5("
    first=1
    tr "a-z" "A-Z" < source.txt | { while IFS=, read data_field data_type size
    do
        if [ "$data_type" != "DATE" ]
        then
            if [ "$first" ]
            then
                first=
            else
                checksum+=" || "
            fi
            checksum+="$data_field"
        fi
    done
    checksum+=")"
    echo "$checksum"
    }
    

    참고하시기 바랍니다진짜두 번 테스트할 필요가 없습니다 if [ "$data_type" != "DATE" ]. 또한 타당한 이유가 없고 수행 중인 작업을 확실히 알고 있지 않는 한
    항상 쉘 변수(예: )에 대한 참조를 인용해야 합니다 ."$data_type"

  • 추가 최적화로 이 first변수를 제거하고 checksum그 자체를 사용하여 루프의 첫 번째 반복을 식별할 수 있습니다.

    checksum=
    tr "a-z" "A-Z" < source.txt | { while IFS=, read data_field data_type size
    do
        if [ "$data_type" != "DATE" ]
        then
            if [ "$checksum" != "" ]
            then
                checksum+=" || "
            fi
            checksum+="$data_field"
        fi
    done
    checksum="md5($checksum)"
    echo "$checksum"
    }
    

답변3

당신이 작성하는 것만큼 복잡한 것은 필요하지 않습니다. 다음을 수행할 수 있습니다.

#!/usr/bin/env bash

checksum="md5("
## Read each line into the fields array (read -a fields), with fields
## separated by commas (IFS=,)
while IFS=, read -a fields
do
    ## If the 2nd element of the array is not "DATE"
    if [ ${fields[1]} != "DATE" ]
    then
        ## Add this to $checksum
        checksum+="${fields[0]} || "
    fi
## The tr is making everything upper case and then feeds
## directly into the while loop.
done < <(tr "a-z" "A-Z" < "$1")
## Get rid of the last || and add the closing ")"
checksum="${checksum% || })"
printf "OUT is: %s\n" "$checksum"

그런 다음 파일을 입력으로 사용하여 스크립트를 실행할 수 있습니다.

$ foo.sh file
OUT is: md5(INSTANCEID || STATUS || NOTES)

관련 정보