我正在编写一个 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)