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

元のシェルパイプラインのすべてのコマンドをcolumns=1つのスクリプトにまとめることができます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"
)

# . . .

読みやすさの点でも、私は複数行形式を好みます。元の文は 1 行でしたが、効率が悪く、私の意見では読みにくいものでした。yomd

これで、入力ファイル (引数 1) からのヘッダーが改行で区切られて変数に格納されました。ループを使用columnsして文字列を反復処理すると、列名が改行で区切られます。$columnsforcut_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シェル配列を使用しないため、配列ループをテストしませんでした。上記の反復処理方法$columnsは、より普遍的で伝統的な方法です。 Arrayは拡張機能であり、すべてのシェルで使用できるわけではありません。

に代入した後はcut_cols、 と同じように反復処理できます$columns

元のファイル データとともに新しいヘッダーを送信するには、新しいヘッダーを印刷し、次に元のファイルの最初の行を除くすべての行を印刷します。これをコマンド グループ (との間{)で実行する}と、両方のコマンドの出力を 1 つのプログラムであるかのようにまとめてリダイレクトできます。

以下は、元のヘッダー行を除いた完全な元のテキスト ファイルと、作成したヘッダー行を生成し、 に送信しstdinます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

変に見えますよね?ここでsed5番目を削除します可能スペース文字以外の文字のシーケンスは、長さがゼロのシーケンスも含め、任意の長さのスペース文字以外の文字のシーケンスを1つのフィールドとしてカウントします。1つは最初のフィールドで、次は後続のスペースとそれに続くスペースの間のヌル文字列です。フィールド 3 と 4 も同様で、5 番目のフィールドは 4 つのスペースが入ったところにあります。かなり厄介なのはわかっています。

printf 'one    two three' |
sed    's|[^ ][^ ]*||2'

one     three

そこには明確なフィールドごとに少なくとも 1 つの非スペース文字に一致するため、sed他のプログラムと似た動作をします。ただし、正規表現の便利な点は、特に編集に適用する場合、出力の動作を非常に具体的に調整できることです。ヌル文字列の処理はすべてその一部にすぎません。

答え3

わかりました。それで、私はこれを理解しました。一部の人を混乱させた質問は、ヘッダー行を取得し、フィールド名の奇妙な部分を編集して、ファイルの先頭に戻すにはどうすればよいかということでした。

最終的に私がやったこと:

  1. ヘッダー行を編集し、変数に割り当てます。
  2. ヘッダー行と残りのテキスト ファイルは常に分離しておきます。

このソリューションは、スクリプトが Vertica テーブルのローダー ツールであるという性質に大きく起因しています。ヘッダー行とファイルから同じフィールドが切り取られている限り、それらが再び 1 つのファイルになっても問題ありません。私は主に、編集したヘッダーを元のコンテンツと再結合して、正しいヘッダー行を含むテキスト ファイルをディレクトリに保存し、ヘッダー行とコンテンツを別々に切り取らなくても済むようにしたいと考えていました。しかし、最終的には次のように別々に切り取ることにしました。

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 でテーブルを作成するには、各フィールドとそのデータ型を識別するステートメントが必要です。私は、create ステートメントの構文で使用される文字列のフィールドとデータ型を変数に設定するいくつかの if ステートメントを介して列変数 (ヘッダー行) を実行することでこれを行います。

次に、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;"

関連情報