在 Bash 脚本内使用 sed 命令行参数和变量

在 Bash 脚本内使用 sed 命令行参数和变量

我有一个 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是比较通用和传统的方法。 Arrays 是一个扩展,并非在每个 shell 中都可用。

分配给之后cut_cols,您可以像 一样对其进行迭代$columns

要发送包含原始文件数据的新标头,请打印新标头,然后打印原始文件除第一行之外的所有行。在命令组中执行此操作(在{和之间}),以便您可以将两个命令的输出一起重定向,就像它们是一个程序一样。

以下生成完整的原始文本文件,不包含原始标题行,但包含您创建的标题行,并将其发送到stdinof 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

好的,所以我明白了。让一些人感到困惑的问题是如何获取标题行,编辑字段名称中的一些怪异之处,然后重新添加到文件中。

我最终做了什么:

  1. 编辑标题行并分配给变量。
  2. 始终将标题行和剩余文本文件分开。

该解决方案很大程度上归因于脚本作为 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;"

相关内容