Menggunakan substitusi parameter pada array Bash


8

Saya memiliki file.txt yang perlu saya baca menjadi array Bash. Maka saya perlu menghapus spasi, tanda kutip ganda dan semua kecuali koma pertama di setiap entri . Inilah sejauh mana saya mendapatkan:

$ cat file.txt
10,this
2 0 , i s
30,"all"
40,I
50,n,e,e,d,2
60",s e,e"

$ cat script.sh
#!/bin/bash
readarray -t ARRAY<$1
ARRAY=( "${ARRAY[@]// /}" )
ARRAY=( "${ARRAY[@]//\"/}" )
for ELEMENT in "${ARRAY[@]}";do
    echo "|ELEMENT|$ELEMENT|"
done

$ ./script.sh file.txt
|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,n,e,e,d,2|
|ELEMENT|60,se,e|

Yang berfungsi hebat kecuali untuk situasi koma. Saya sadar bahwa ada beberapa cara untuk menguliti kucing ini, tetapi karena skrip yang lebih besar ini adalah bagian dari, saya benar-benar ingin menggunakan substitusi parameter untuk sampai ke sini:

|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,need2|
|ELEMENT|60,see|

Apakah ini mungkin melalui substitusi parameter?


3
Apakah ada alasan Anda perlu menyimpan teks dalam array, dan mengapa Anda tidak bisa membiarkan mis awkatau sedmelakukan pemrosesan data?
Kusalananda

@ Jeff - Melompati array akan menjadi mimpi buruk untuk diterapkan dalam skrip yang lebih besar yang saya kerjakan.
Jon Red

3
@JonRed Saya tidak tahu apa yang Anda lakukan, jadi sangat mungkin bahwa Anda mungkin tidak punya pilihan dalam masalah ini, tetapi secara umum, ketika Anda menemukan diri Anda melakukan akrobat string yang begitu rumit di shell, itu indikasi yang sangat baik bahwa Anda harus menggunakan bahasa pemrograman yang sebenarnya. Shell tidak dirancang sebagai bahasa pemrograman, dan meskipun dapat digunakan sebagai bahasa pemrograman, shell sebenarnya bukan ide yang baik untuk hal-hal yang lebih kompleks. Saya sangat menyarankan Anda untuk mempertimbangkan beralih ke perl atau python atau bahasa scripting lainnya.
terdon

@terdon Itu lucu, saya baru saja selesai mengatakan hal yang persis sama kepada rekan saya sebelum saya membaca posting ini. Saya pada dasarnya mengatakan ini adalah versi terakhir dari skrip ini dan bahwa persyaratan lebih lanjut akan mengharuskan penulisan ulang di Perl. Jadi ya, saya setuju
Jon Red

Jawaban:


9

Saya akan menghapus apa yang perlu Anda hapus menggunakan sed sebelum memuat ke dalam array (juga perhatikan nama variabel huruf kecil, secara umum yang terbaik adalah menghindari variabel kapital dalam skrip shell):

#!/bin/bash
readarray -t array< <(sed 's/"//g; s/  *//g; s/,/"/; s/,//g; s/"/,/' "$1")
for element in "${array[@]}";do
    echo "|ELEMENT|$element|"
done

Ini menghasilkan output berikut pada file contoh Anda:

$ foo.sh file 
|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,need2|
|ELEMENT|60,see|

Jika Anda benar-benar harus menggunakan substitusi parameter, coba sesuatu seperti ini:

#!/bin/bash
readarray -t array< "$1"
array=( "${array[@]// /}" )
array=( "${array[@]//\"/}" )
array=( "${array[@]/,/\"}" )
array=( "${array[@]//,/}" )
array=( "${array[@]/\"/,}" )

for element in "${array[@]}"; do
    echo "|ELEMENT|$element|"
done

1
@ JonRed Saya menambahkan versi dengan substitusi parameter tetapi rumit, rumit dan jelek. Melakukan hal semacam ini di shell jarang merupakan ide yang bagus.
terdon

1
Perhatikan bahwa jika Anda telah menghapus spasi dan tanda kutip ganda, karakter ini menjadi tersedia untuk digunakan sebagai pengganti Anda RANDOMTEXTTHATWILLNEVERBEINTHEFILE.
Kusalananda

1
@ Kusalananda ya, saya baru saja membaca jawaban Anda. Seharusnya memikirkan itu! Terima kasih :)
terdon

Langsung menjawab pertanyaan, mengilustrasikan mengapa solusi pilihan saya tidak ideal, dan memberikan alternatif yang paling layak. Anda menang, jawaban terbaik.
Jon Red

10

Sejauh yang saya bisa lihat, tidak perlu membacanya ke dalam basharray untuk membuat output:

$ sed 's/[ "]//g; s/,/ /; s/,//g; s/ /,/; s/.*/|ELEMENT|&|/' <file
|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,need2|
|ELEMENT|60,see|

The sedekspresi menghapus spasi dan tanda kutip ganda, menggantikan koma pertama dengan ruang (tidak ada ruang lain dalam string pada saat ini), menghapus semua koma lainnya, mengembalikan koma pertama, dan prepends dan menambahkan data tambahan.

Atau, dengan GNU sed:

sed 's/[ "]//g; s/,//2g; s/.*/|ELEMENT|&|/' <file

(standar sedtidak mendukung kombinasi 2dan gsebagai tanda pada sperintah).


1
dengan GNU sed, Anda dapat menggunakan 's/,//2guntuk menghapus koma, dimulai dengan 2nd
glenn jackman

2
Dan, 2 s /// perintah terakhir bisa s/.*/|ELEMENT|&|/tetapi itu mungkin lebih banyak upaya untuk sed.
glenn jackman

1
@glennjackman Mungkin, tetapi terlihat agak rapi.
Kusalananda

Ya, ini bagian dari skrip yang lebih besar. Array diperlukan, bukan hanya untuk output. Karena itu saya tertarik pada substitusi parameter. Saya bisa mengulang array dengan ini tetapi itu akan menjadi mimpi buruk untuk diterapkan. Terndon memberikan solusi loop-bebas menggunakan sed yang saya kemungkinan akan jatuh kembali jika penggantian parameter adalah no-go.
Jon Red

Namun, jika saya tidak terikat menggunakan array, ini akan menjadi solusi terbaik.
Jon Red

9
ELEMENT='50,n,e,e,d,2'
IFS=, read -r first rest <<<"$ELEMENT"
printf "%s,%s\n" "$first" "${rest//,/}"
50,need2

Keluar dari kebiasaan menggunakan nama variabel ALLCAPS. Anda akhirnya akan bertabrakan dengan variabel "sistem" penting seperti PATH dan memecah kode Anda.


Bukan substitusi parameter. TETAPI, saya tidak menyadari bahwa nama variabel ALLCAPS adalah kebiasaan buruk di Bash. Anda membuat poin yang baik, yang pasti dikonfirmasi oleh Google. Terima kasih telah meningkatkan gaya saya! :)
Jon Red

1
Saya sudah menjawab pertanyaan di mana orang itu menulis PATH=something; ls $PATHdan kemudian bertanya-tanya tentang ls: command not foundkesalahannya.
glenn jackman

1
Ada hampir seratus variabel bawaan yang diberi nama dalam semua huruf besar (klik tautan halaman manual ini ) untuk melihat ...
Jeff Schaller

8

[Ini pada dasarnya adalah versi yang lebih lengkap dari jawaban glenn jackmann ]

Membangun array asosiatif dari kunci dan nilai yang dilucuti, menggunakan koma pertama sebagai pemisah:

declare -A arr
while IFS=, read -r k v; do arr["${k//[ \"]}"]="${v//[ ,\"]}"; done < file.txt
for k in "${!arr[@]}"; do 
  printf '|ELEMENT|%s,%s|\n' "$k" "${arr[$k]}"
done
|ELEMENT|20,is|
|ELEMENT|10,this|
|ELEMENT|50,need2|
|ELEMENT|40,I|
|ELEMENT|60,see|
|ELEMENT|30,all|

6

Anda bisa mengulang array dan menggunakan variabel perantara:

for((i=0; i < "${#ARRAY[@]}"; i++))
do
  rest="${ARRAY[i]#*,}"
  ARRAY[i]="${ARRAY[i]%%,*}","${rest//,/}"
done

Ini menetapkan restbagian setelah koma pertama; kami kemudian menggabungkan tiga bagian kembali ke variabel asli:

  • bagian sebelum koma pertama
  • koma
  • penggantian restsetiap koma tanpa apa pun

Ini adalah pemikiran pertama saya dan cukup sederhana untuk contoh tetapi ini adalah bagian dari skrip yang lebih besar di mana array sangat besar dan sudah ada loop dan itu akan menjadi keseluruhan. Ini pasti akan berhasil tetapi akan sangat sulit untuk diterapkan dalam proyek yang lebih besar yang sedang saya kerjakan.
Jon Red

1
Cukup adil; Saya hanya mencoba menjawab dalam batasan (hanya perluasan parameter).
Jeff Schaller
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.