shell 中的字符串操作

shell 中的字符串操作

我正在编写一个 shell 脚本来生成特定格式的字符串,以便它可以用作我正在使用的 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,但如果最后几行的类型为日期,这又会失败。

我怎样才能去掉||最后出现的which?

答案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值得注意的是,在 shell 脚本中  很少有用。$(cat source.txt | wc -l)是经典无用的使用cat;如果您需要计算文件中的行数, $(wc -l < source.txt)这是一种更简洁的方法。
  • 但你不需要计算 中的行数source.txt
  • file=$(cat source.txt)是一种丑陋的读取文件的方式;
    在阅读时……
        ︙
    完成<文件名
    更好。  read这样做的好处是它可以为您将行拆分为字段。
  • tr当您只需要对整个文件运行一次时,为文件的每一行运行两次是愚蠢的。在某些情况下,
    tr … <文件名|在阅读时……
        ︙
    完毕
    效果很好。但这有一个问题:while循环在子 shell 中运行,因此您对 shell 变量(例如checksum)所做的更改在循环结束后将不可见。 Terdon 展示了一种解决该问题的方法;这是另一个:
    tr … <文件名|{在阅读时……
            ︙
        可能改变的命令校验和。
            ︙
    完毕
        ︙
    使用的命令$校验和。
        ︙
    }
  • 正如您所发现的,确定某件事最后出现的位置可能很困难。通常更容易识别第一个:

    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" ]两次。
    另请注意,您应该始终引用对 shell 变量的引用(例如,"$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)

相关内容