シェルで文字列を操作する

シェルで文字列を操作する

私は、作業中のXMLの1つへの入力として使用できるように、特定の形式の文字列を生成するシェルスクリプトを作成しています。

次の形式の入力ファイルが提供されます。<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ファイル全体に対して一度だけ実行するだけですが、ファイルの各行に対して2回実行するのは愚かです。場合によっては、
    ト… <ファイル名|読みながら...
    する
        ︙
    完璧
    良い結果。しかし、問題があります。whileループはサブシェルで実行されるため、シェルchecksum変数(たとえば)への変更はループの終了後には表示されません。 Terdonはこの問題を解決する1つの方法を示しています。
    ト… <ファイル名|{読みながら…
    する
            ︙
        変更される可能性のある注文チェックサム。
            ︙
    完璧
        ︙
    使用されるコマンド$チェック島。
        ︙
    }
  • ご存知のように、最後に発生した問題がどこにあるかを把握するのは難しいかもしれません。通常、最初の項目を識別する方が簡単です。

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

    参考にしてください本物2回テストする必要はありません 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)

関連情報