我有一个 bash 脚本,它需要一堆命令行参数。在这种情况下,唯一重要的是第一个 $1,它是一个文本文件。
标头很长,下面是一些字段的示例。
COL0___LINE_NUMBER
COL1_AFF_ID
COL2_FULL_NAME
COL3_ADDRESS
BDID
BEST_STATE
COL48_LATITUDE
COL49_LONGITUDE
我需要更改标题行,我可以使用下面的代码来完成此操作。这确实实现了我想要的,但是考虑到这是我第一次编写 bash 脚本,欢迎任何保留下面输出中的变量的风格更改等。
columns=`cat $1 | head -1 |sed 's/-/_/g' | sed 's/ /_/g' |
sed 's/COL[0-9]\+_BDID/DROP_BDID/g' | sed 's/COL[0-9]\+_//g' |
tr '\t' '\n' | tr "[:lower:]" "[:upper:]"`
注意:换行符的选项卡的格式纯粹是为了在回显列标题时尝试美观。这既是为了我自己的可读性,也是为了回显 vertica create table 语句的脚本用户的可读性。
不管怎样,现在我想让列变量成为我的文本文件的标题行,以便我可以在脚本中使用新版本。所以,我想要完整的原始文本文件没有它是原始标题行,并且是我创建的标题行,因此以下内容指的是我的文件的编辑版本,
col_arr=($columns)
cut_cols = ""
for i in ${!col_arr[@]}; do
if [[ "${col_arr[$i]}" =~ ^(__LINE_NUMBER|CONFIDENCE|DROP_BDID|LINE_NUMBER|ZIP9|ZIP9|ZIP9MATCH)$ ]]; then
echo "$i"
#haven't written yet, but this will add to cut_cols so that
#I can remove the above listed columns in the text file
#based on their index.
fi
done
/opt/vertica/bin/vsql -U ${4} -w ${5} -h ${database} \
-c "copy $schema.$table from STDIN delimiter E'\t' direct no escape;"
答案1
我们可以将原始 shell 管道中的所有命令合并columns=
到一个sed
脚本中。该sed
脚本仅修改输入的第一行,然后退出。下面的做法是确切地columns=
与原始问题中的相同:
columns=$(
sed '
1 { # execute block on line 1
s/-/_/g
s/ /_/g
s/COL[0-9]\+_BDID/DROP_BDID/g
s/COL[0-9]\+_//g
s/\t/\n/g
y/abcdefghijklmnopqrstuv/ABCDEFGHIJKLMNOPQRSTUV/
q # quit after line 1
}
' "$1"
)
# . . .
我更喜欢多行格式,也是为了可读性。尽管最初的声明只有一行,但它的效率要低得多,而且在我看来,更难以阅读。约姆德
现在您已经从输入文件 (arg 1) 中获取了标头,这些标头存储在由换行符分隔的变量中columns
。您可以$columns
使用循环迭代字符串for
,这将用换行符分隔列名cut_cols
:
cut_cols="$(
for col in $columns
do
case $col in
(*__LINE_NUMBER*|*CONFIDENCE*|*DROP_BDID*|*LINE_NUMBER*|*ZIP9*|*ZIP9MATCH*)
echo "$col"
;;
esac
done
)"
根据您的喜好,这会做同样的事情:
cut_cols=
for col in $columns
do
case $col in
(*__LINE_NUMBER*|*CONFIDENCE*|*DROP_BDID*|*LINE_NUMBER*|*ZIP9*|*ZIP9MATCH*)
cut_cols="$cut_cols $col"
;;
esac
done
cut_cols=$(echo "$cut_cols" | sed 's/^ *//; s/ /\n/g')
我没有测试你的数组循环,cut_cols
因为我不使用 shell 数组。上述迭代的方法$columns
是比较通用和传统的方法。 Array
s 是一个扩展,并非在每个 shell 中都可用。
分配给之后cut_cols
,您可以像 一样对其进行迭代$columns
。
要发送包含原始文件数据的新标头,请打印新标头,然后打印原始文件除第一行之外的所有行。在命令组中执行此操作(在{
和之间}
),以便您可以将两个命令的输出一起重定向,就像它们是一个程序一样。
以下生成完整的原始文本文件,不包含原始标题行,但包含您创建的标题行,并将其发送到stdin
of vsql
:
# . . .
{ # start command group
echo "$columns" | tr '\n' '\t'; # print with tabs instead of newlines
echo # add newline record separator
sed 1d "$1" # print all but 1st line of "$1"
} | # pipe as one file to vsql
/opt/vertica/bin/vsql -U ${4} -w ${5} -h ${database} \
-c "copy $schema.$table from STDIN delimiter E'\t' direct no escape;"
答案2
这个问题我实在是不太明白(特别是只编辑文件中的列标题行的原因 - 之后它用来识别的所有行会发生什么?),但这部分是有道理的:
#haven't written yet, but this will add to cut_cols so that
#I can remove the above listed columns in the text file
#based on their index.
我明白了。以下是sed
从文件中提取特定字段的一些技巧:
printf 'one two three' |
sed 's|[^ ]*||5'
one three
这看起来很奇怪,对吧?这里sed
删除了第5个可能的非空格字符序列,它将任何长度的非空格字符序列计为单个字段 - 以包括零长度序列。所以一是第一个字段,下一个是后续空格和其后的空格之间的空字符串,字段 3 和字段 4 也是如此,第五个字段是 4 个空格。我知道,这很粗糙。
printf 'one two three' |
sed 's|[^ ][^ ]*||2'
one three
我在那里包括一个定每个字段至少匹配一个非空格字符,因此其sed
行为更像其他一些程序。不过,正则表达式的便利之处在于,尤其是在应用于编辑时,您可以非常具体地定制输出的行为,而处理空字符串只是其中的一部分。
答案3
好的,所以我明白了。让一些人感到困惑的问题是如何获取标题行,编辑字段名称中的一些怪异之处,然后重新添加到文件中。
我最终做了什么:
- 编辑标题行并分配给变量。
- 始终将标题行和剩余文本文件分开。
该解决方案很大程度上归因于脚本作为 Vertica 表的加载工具的性质。只要从标题行和文件中删除相同的字段,它们是否再次成为一个文件并不重要。我最想将编辑后的标题与其原始内容重新组合,以便我可以在目录中保存具有正确标题行的文本文件,这样我就不必单独剪切标题行和内容。然而,我最终还是像这样将它们分开切割,
col_arr=($columns)
cut_cols=""
for i in ${!col_arr[@]}; do
if ! [[ "${col_arr[$i]}" =~ ^(__LINE_NUMBER|CONFIDENCE|DROP_BDID|LINE_NUMBER|ZIP9|ZIP9|ZIP9MATCH)$ ]]; then
ind=$(($i+1))
cut_cols="$cut_cols,$ind"
fi
done
cut_cols=$(echo $cut_cols | sed s/^,//g)
columns=$(echo "$columns" | cut -f "$cut_cols")
cut -f ${cut_cols} ${1}>member_temp.txt
sed -i 1d member_temp.txt
我决定为列维护一个变量来自于将此脚本用作加载程序。在 Vertica 中创建表需要一个标识每个字段及其数据类型的语句。为此,我通过一些 if 语句运行列变量(标题行),这些语句使用要在 create 语句的语法中使用的字符串中的字段和数据类型填充变量。
然后,我将 member_temp.txt 加载到之前创建的表中。没有标题行并不重要,因为无论如何我都会将其删除,因为我不希望它存储在我的表中。
cat member_temp.txt | /opt/vertica/bin/vsql -U ${4} -w ${5} -h ${database} \
-c "copy $schema.$table from STDIN delimiter E'\t' direct no escape;"