在 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;"

相關內容