
我有一個 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;"