Pada artikel sebelumnya, kita telah membahas tentang perintah cut yang dapat digunakan untuk mengekstrak kolom dari file data teks CSV atau tabular.
paste
perintah melakukan kebalikannya:menggabungkan beberapa file input untuk menghasilkan file teks baru yang dibatasi dari mereka. Kita akan melihat cara efektif menggunakan perintah Tempel di Linux dan Unix.
7 Contoh praktis perintah Tempel di Linux
Jika Anda lebih suka video, Anda dapat menonton video ini menjelaskan contoh perintah Tempel yang sama yang dibahas dalam artikel ini.
1. Menempelkan kolom
Dalam kasus penggunaan paling dasar, paste
perintah mengambil N masukan file dan gabungkan baris demi baris pada output:
sh$ printf "%s\n" {a..e} | tee letters
a
b
c
d
e
sh$ printf "%s\n" {1..5} | tee digits
1
2
3
4
5
sh$ paste letters digits
a 1
b 2
c 3
d 4
e 5
Tapi mari kita tinggalkan sekarang penjelasan teoretis untuk mengerjakan contoh praktis. Jika Anda telah mengunduh file sampel yang digunakan dalam video di atas, Anda dapat melihat saya memiliki beberapa file data yang sesuai dengan berbagai kolom dari sebuah tabel:
sh$ head -3 *.csv
==> ACCOUNTLIB.csv <==
ACCOUNTLIB
TIDE SCHEDULE
VAT BS/ENC
==> ACCOUNTNUM.csv <==
ACCOUNTNUM
623477
445452
==> CREDIT.csv <==
CREDIT
<--- empty line
<--- empty line
==> DEBIT.csv <==
DEBIT
00000001615,00
00000000323,00
Cukup mudah untuk menghasilkan file teks tab-delimited dari data tersebut:
sh$ paste *.csv | head -3
ACCOUNTLIB ACCOUNTNUM CREDIT DEBIT
TIDE SCHEDULE 623477 00000001615,00
VAT BS/ENC 445452 00000000323,00
Seperti yang Anda lihat, saat ditampilkan di konsol, konten file nilai yang dipisahkan tab tersebut tidak menghasilkan tabel yang diformat dengan sempurna. Tapi ini adalah desain:paste
perintah tidak digunakan untuk membuat file teks dengan lebar tetap, tetapi hanya file teks yang dibatasi di mana satu karakter tertentu ditetapkan sebagai pemisah bidang.
Jadi, meskipun tidak jelas pada output di atas, sebenarnya ada satu dan hanya satu karakter tab di antara setiap bidang. Mari kita nyatakan dengan menggunakan perintah sed:
sh$ paste *.csv | head -3 | sed -n l
ACCOUNTLIB\tACCOUNTNUM\tCREDIT\tDEBIT$
TIDE SCHEDULE\t623477\t\t00000001615,00$
VAT BS/ENC\t445452\t\t00000000323,00$
Sekarang, karakter yang tidak terlihat ditampilkan dengan jelas di output. Dan Anda dapat melihat karakter tab ditampilkan sebagai \t
. Anda dapat menghitungnya:selalu ada tiga tab di setiap jalur keluaran— satu di antara setiap bidang. Dan ketika Anda melihat dua dari mereka berturut-turut, itu hanya berarti ada bidang kosong di sana. Ini sering terjadi pada file contoh khusus saya karena pada setiap baris, bidang KREDIT atau DEBIT disetel, tetapi tidak pernah keduanya secara bersamaan.
2. Mengubah pembatas bidang
Seperti yang telah kita lihat, paste
perintah menggunakan karakter tab sebagai pemisah bidang default ("pembatas"). Sesuatu yang dapat kita ubah menggunakan -d
pilihan. Katakanlah saya ingin menggunakan titik koma sebagai gantinya:
# The quotes around the ';' are used to prevent the
# shell to consider that semi-colon as being a command separator
sh$ paste -d ';' *.csv | head -3
ACCOUNTLIB;ACCOUNTNUM;CREDIT;DEBIT
TIDE SCHEDULE;623477;;00000001615,00
VAT BS/ENC;445452;;00000000323,00
Tidak perlu menambahkan sed
perintah di akhir pipa di sini karena pemisah yang kami gunakan adalah karakter yang dapat dicetak. Bagaimanapun, hasilnya sama:pada baris tertentu, setiap bidang dipisahkan dari tetangganya dengan menggunakan pembatas satu karakter.
3. Memindahkan data menggunakan mode serial
Contoh di atas memiliki satu kesamaan:paste
perintah membaca semua file inputnya secara paralel, sesuatu yang diperlukan agar dapat menggabungkannya secara baris demi baris dalam output.
Tapi paste
perintah juga dapat beroperasi dalam apa yang disebut mode serial , diaktifkan menggunakan -s
bendera. Seperti namanya, dalam mode serial, paste
perintah akan membaca file input satu demi satu. Isi dari file input pertama akan digunakan untuk menghasilkan baris output pertama. Kemudian isi dari file input kedua akan digunakan untuk menghasilkan baris output kedua, dan seterusnya. Itu juga berarti output akan memiliki baris sebanyak file yang ada di input.
Secara lebih formal, data diambil dari file N akan muncul sebagai N baris th dalam output dalam mode serial, sedangkan itu akan muncul sebagai N kolom th dalam mode "paralel" default. Dalam istilah matematika, tabel yang diperoleh dalam mode serial adalah transpos tabel yang dihasilkan dalam mode default (dan sebaliknya ).
Untuk mengilustrasikannya, mari pertimbangkan subsampel kecil dari data kita:
sh$ head -5 ACCOUNTLIB.csv | tee ACCOUNTLIB.sample
ACCOUNTLIB
TIDE SCHEDULE
VAT BS/ENC
PAYABLES
ACCOMMODATION GUIDE
sh$ head -5 ACCOUNTNUM.csv | tee ACCOUNTNUM.sample
ACCOUNTNUM
623477
445452
4356
623372
Dalam mode default ("paralel"), data file input akan berfungsi sebagai kolom dalam output, menghasilkan tabel dua kolom dengan lima baris:
sh$ paste *.sample
ACCOUNTLIB ACCOUNTNUM
TIDE SCHEDULE 623477
VAT BS/ENC 445452
PAYABLES 4356
ACCOMMODATION GUIDE 623372
Tetapi dalam mode serial, data file input akan muncul sebagai baris, sekarang menghasilkan tabel lima kolom dengan dua baris:
sh$ paste -s *.sample
ACCOUNTLIB TIDE SCHEDULE VAT BS/ENC PAYABLES ACCOMMODATION GUIDE
ACCOUNTNUM 623477 445452 4356 623372
4. Bekerja dengan masukan standar
Seperti banyak utilitas standar, paste
perintah dapat menggunakan input standar untuk membaca data. Baik secara implisit ketika tidak ada nama file yang diberikan sebagai argumen, atau secara eksplisit dengan menggunakan -
. khusus nama file. Ternyata, ini tidak terlalu berguna:
# Here, the paste command is useless
head -5 ACCOUNTLIB.csv | paste
ACCOUNTLIB
TIDE SCHEDULE
VAT BS/ENC
PAYABLES
ACCOMMODATION GUIDE
Saya mendorong Anda untuk mengujinya sendiri, tetapi sintaks berikut akan menghasilkan hasil yang sama— membuat sekali lagi perintah tempel tidak berguna dalam kasus itu:
head -5 ACCOUNTLIB.csv | paste -
Jadi, apa gunanya membaca data dari input standar? Nah, dengan -s
bendera, segalanya menjadi jauh lebih menarik seperti yang akan kita lihat sekarang.
4.1. Menggabungkan baris file
Seperti yang telah kita lihat beberapa paragraf sebelumnya, dalam mode serial perintah tempel akan menulis semua baris file input pada baris output yang sama. Ini memberi kita cara sederhana untuk menggabungkan semua baris yang dibaca dari input standar menjadi hanya satu (berpotensi sangat panjang) baris output:
sh$ head -5 ACCOUNTLIB.csv | paste -s -d':'
ACCOUNTLIB:TIDE SCHEDULE:VAT BS/ENC:PAYABLES:ACCOMMODATION GUIDE
Ini sebagian besar adalah hal yang sama yang dapat Anda lakukan menggunakan tr
perintah, tetapi dengan satu perbedaan sekalipun. Mari kita gunakan diff
utilitas untuk mengetahui bahwa:
sh$ diff <(head -5 ACCOUNTLIB.csv | paste -s -d':') \
<(head -5 ACCOUNTLIB.csv | tr '\n' ':')
1c1
< ACCOUNTLIB:TIDE SCHEDULE:VAT BS/ENC:PAYABLES:ACCOMMODATION GUIDE
---
> ACCOUNTLIB:TIDE SCHEDULE:VAT BS/ENC:PAYABLES:ACCOMMODATION GUIDE:
\ No newline at end of file
Seperti yang dilaporkan oleh diff
utilitas, kita dapat melihat tr
perintah telah menggantikan setiap contoh karakter baris baru dengan pembatas yang diberikan, termasuk yang terakhir. Di sisi lain, paste
perintah membuat karakter baris baru terakhir tidak tersentuh. Jadi tergantung apakah Anda memerlukan pembatas setelah kolom terakhir atau tidak, Anda akan menggunakan satu perintah atau yang lain.
4.2. Pemformatan multi-kolom dari satu file input
Menurut spesifikasi Grup Terbuka, “masukan standar harus dibaca satu baris setiap kali” dengan paste
memerintah. Jadi, melewati beberapa kemunculan -
nama file khusus sebagai argumen untuk paste
perintah akan menghasilkan banyak baris input yang berurutan ditulis ke dalam baris output yang sama:
sh$ seq 9 | paste - - -
1 2 3
4 5 6
7 8 9
Untuk memperjelas, saya mendorong Anda untuk mempelajari perbedaan antara dua perintah di bawah ini. Dalam kasus pertama, perintah tempel membuka tiga kali file yang sama, menghasilkan duplikasi data dalam output. Di sisi lain, dalam kasus kedua, file ACCOUNTLIB dibuka hanya sekali (oleh shell), tetapi dibaca tiga kali untuk setiap baris (dengan paste
perintah), sehingga konten file ditampilkan sebagai tiga kolom:
sh$ paste ACCOUNTLIB.csv ACCOUNTLIB.csv ACCOUNTLIB.csv | head -2
ACCOUNTLIB ACCOUNTLIB ACCOUNTLIB
TIDE SCHEDULE TIDE SCHEDULE TIDE SCHEDULE
sh$ paste - - - < ACCOUNTLIB.csv | head -2
ACCOUNTLIB TIDE SCHEDULE VAT BS/ENC
PAYABLES ACCOMMODATION GUIDE VAT BS/ENC
Mengingat perilaku paste
perintah ketika membaca dari input standar, biasanya tidak disarankan untuk menggunakan beberapa -
nama file khusus dalam mode serial. Dalam hal ini, kemunculan pertama akan membaca input standar hingga akhir, dan kemunculan berikutnya dari -
akan membaca dari aliran input yang sudah habis— sehingga tidak ada lagi data yang tersedia:
# The following command will produce 3 lines of output.
# But the first one exhausted the standard input,
# so the remaining two lines are empty
sh$ seq 9 | paste -s - - -
1 2 3 4 5 6 7 8 9
5. Bekerja dengan file dengan panjang berbeda
Spesifikasi Open Group untuk paste
utilitas cukup jelas:
Jika kondisi akhir file terdeteksi pada satu atau lebih file input, tetapi tidak semua file input, tempel akan berperilaku seolah-olah baris kosong dibaca dari file di mana akhir file terdeteksi, kecuali jika -s opsi ditentukan.
Jadi, perilakunya adalah apa yang Anda harapkan:data yang hilang digantikan oleh konten "kosong". Untuk mengilustrasikan perilaku itu, mari kita catat beberapa transaksi lagi ke dalam "database" kita. Untuk menjaga file asli tetap utuh, kami akan mengerjakan salinan data kami:
# Copy files
sh$ for f in ACCOUNTNUM ACCOUNTLIB CREDIT DEBIT; do
cp ${f}.csv NEW${f}.csv
done
# Update the copy
sh$ cat - << EOF >> NEWACCOUNTNUM.csv
1080
4356
EOF
sh$ cat - << EOF >> NEWDEBIT.csv
00000001207,35
EOF
sh$ cat - << EOF >> NEWCREDIT.csv
00000001207,35
EOF
Dengan pembaruan tersebut, kami sekarang telah mendaftarkan perpindahan modal baru dari akun #1080 ke akun #4356. Namun, seperti yang mungkin Anda perhatikan, saya tidak repot-repot memperbarui file ACCOUNTLIB. Ini sepertinya bukan masalah besar karena paste
perintah akan mengganti baris yang hilang dengan data kosong:
sh$ paste -d';' NEWACCOUNTNUM.csv \
NEWACCOUNTLIB.csv \
NEWDEBIT.csv \
NEWCREDIT.csv | tail
4356;PAYABLES;;00000000402,03
613866;RENTAL COSTS;00000000018,00;
4356;PAYABLES;;00000000018,00
657991;MISCELLANEOUS CHARGES;00000000015,00;
445333;VAT BS/DEBIT;00000000003,00;
4356;PAYABLES;;00000000018,00
626510;LANDLINE TELEPHONE;00000000069,14;
445452;VAT BS/ENC;00000000013,83;
1080;;00000001207,35; # <-- the account label is missing here
4356;;;00000001207,35 # <-- the account label is missing here
Tapi hati-hati, paste
perintah hanya dapat mencocokkan baris dengan fisik position:yang bisa dilihatnya hanyalah sebuah file "lebih pendek" dari yang lain. Bukan di mana datanya hilang. Jadi selalu menambahkan bidang kosong di akhir output, sesuatu yang dapat menyebabkan offset tak terduga dalam data Anda. Mari kita perjelas dengan menambahkan transaksi lain:
sh$ cat << EOF >> NEWACCOUNTNUM.csv
4356
3465
EOF
sh$ cat << EOF >> NEWACCOUNTLIB.csv
PAYABLES
WEB HOSTING
EOF
sh$ cat << EOF >> NEWDEBIT.csv
00000000706,48
EOF
sh$ cat << EOF >> NEWCREDIT.csv
00000000706,48
EOF
Kali ini, saya lebih teliti karena saya memperbarui nomor rekening (ACCOUNTNUM) dengan benar, dan label yang sesuai (ACCOUNTLIB) serta file data KREDIT dan DEBIT. Tapi karena ada data yang hilang di record sebelumnya, paste
perintah tidak lagi dapat menyimpan bidang terkait pada baris yang sama:
sh$ paste -d';' NEWACCOUNTNUM.csv \
NEWACCOUNTLIB.csv \
NEWDEBIT.csv \
NEWCREDIT.csv | tail
4356;PAYABLES;;00000000018,00
657991;MISCELLANEOUS CHARGES;00000000015,00;
445333;VAT BS/DEBIT;00000000003,00;
4356;PAYABLES;;00000000018,00
626510;LANDLINE TELEPHONE;00000000069,14;
445452;VAT BS/ENC;00000000013,83;
1080;PAYABLES;00000001207,35;
4356;WEB HOSTING;;00000001207,35
4356;;;00000000706,48
3465;;00000000706,48;
Seperti yang Anda lihat, akun #4356 dilaporkan dengan label “WEB HOSTING” padahal, pada kenyataannya, yang terakhir akan muncul di baris yang sesuai dengan akun #3465.
Sebagai kesimpulan, jika Anda harus berurusan dengan data yang hilang, alih-alih paste
perintah yang harus Anda pertimbangkan untuk menggunakan join
utilitas karena yang terakhir akan cocok dengan baris berdasarkan kontennya, dan bukan berdasarkan posisi yang ada di file input. Itu membuatnya jauh lebih cocok untuk aplikasi gaya "database". Saya sudah memublikasikan video tentang join
perintah, tapi itu mungkin pantas mendapatkan artikelnya sendiri, jadi beri tahu kami jika Anda tertarik dengan topik itu!
6. Bersepeda melewati pembatas
Dalam sebagian besar kasus penggunaan, Anda hanya akan memberikan satu karakter sebagai pembatas. Inilah yang kami lakukan sampai sekarang. Namun, jika Anda memberikan beberapa karakter setelah -d
opsi, perintah tempel akan menggilirnya:karakter pertama akan digunakan sebagai pembatas kolom pertama pada baris, karakter kedua sebagai pembatas kolom kedua, dan seterusnya.
sh$ paste -d':+-' ACCOUNT*.csv CREDIT.csv DEBIT.csv | head -5
ACCOUNTLIB:ACCOUNT NUM+CREDIT-DEBIT
TIDE SCHEDULE:623477+-00000001615,00
VAT BS/ENC:445452+-00000000323,00
PAYABLES:4356+00000001938,00-
ACCOMODATION GUIDE:623372+-00000001333,00
Pembatas bidang hanya dapat muncul antara bidang. Tidak di akhir baris. Dan Anda tidak dapat menyisipkan lebih dari satu pembatas di antara dua bidang yang diberikan. Sebagai trik untuk mengatasi keterbatasan ini, Anda dapat menggunakan /dev/null
file khusus sebagai input tambahan di mana Anda memerlukan pemisah tambahan:
# Display the opening bracket between the
# ACCOUNTLIB field and the ACCOUNTNUM field, and
# the closing bracket between the ACCOUNTNUM field
# and the empty `/dev/null` field:
sh$ paste -d'()' \
ACCOUNT*.csv /dev/null | head -5
ACCOUNTLIB(ACCOUNTNUM)
TIDE SCHEDULE(623477)
VAT BS/ENC(445452)
PAYABLES(4356)
ACCOMODATION GUIDE(623372)
Sesuatu yang Anda bahkan mungkin menyalahgunakan:
sh$ paste -d'# is ' \
- ACCOUNTNUM.csv - - - ACCOUNTLIB.csv < /dev/null | tail -5
#657991 is MISCELLANEOUS CHARGES
#445333 is VAT BS/DEBIT
#4356 is PAYABLES
#626510 is LANDLINE TELEPHONE
#445452 is VAT BS/ENC
Namun, tidak perlu dikatakan, jika Anda mencapai tingkat kerumitan itu, itu mungkin petunjuk paste
utilitas belum tentu merupakan alat terbaik untuk pekerjaan itu. Mungkin layak dipertimbangkan, dalam hal ini, sesuatu yang lain seperti sed
atau perintah awk.
Tetapi bagaimana jika daftar berisi pembatas lebih sedikit dari yang dibutuhkan untuk menampilkan baris dalam output? Menariknya, paste
perintah akan "berputar" di atasnya. Jadi, setelah daftar habis, paste
perintah akan melompat kembali ke pembatas pertama, sesuatu yang mungkin membuka pintu untuk beberapa penggunaan kreatif. Bagi saya sendiri, saya tidak dapat membuat sesuatu yang benar-benar berguna dengan fitur yang diberikan data saya. Jadi, Anda harus puas dengan contoh yang agak mengada-ada berikut ini. Tapi itu tidak akan membuang-buang waktu Anda karena itu adalah kesempatan yang baik untuk menyebutkan Anda harus menggandakan garis miring terbalik (\\
) bila Anda ingin menggunakannya sebagai pembatas:
sh$ paste -d'/\\' \
- ACCOUNT*.csv CREDIT.csv DEBIT.csv - < /dev/null | tail -5
/MISCELLANEOUS CHARGES\657991/\00000000015,00/
/VAT BS/DEBIT\445333/\00000000003,00/
/PAYABLES\4356/00000000018,00\/
/LANDLINE TELEPHONE\626510/\00000000069,14/
/VAT BS/ENC\445452/\00000000013,83/
7. Pembatas karakter multibyte
Seperti kebanyakan utilitas Unix standar, perintah tempel lahir pada saat satu karakter setara dengan satu byte. Tapi ini tidak lagi terjadi:hari ini, banyak sistem menggunakan pengkodean panjang variabel UTF-8 secara default. Dalam UTF-8, sebuah karakter dapat diwakili oleh 1, 2, 3 atau 4 byte. Itu memungkinkan kami untuk menggabungkan dalam file teks yang sama seluruh variasi tulisan manusia— serta berton-ton simbol dan emoji— sambil mempertahankan kompatibilitas yang meningkat dengan pengkodean karakter US-ASCII satu byte yang lama.
Katakanlah misalnya saya ingin menggunakan WHITE DIAMOND (◇ U+25C7) sebagai pemisah bidang saya. Dalam UTF-8, karakter ini dikodekan menggunakan tiga byte e2 97 87
. Karakter ini mungkin sulit didapatkan dari keyboard, jadi jika Anda ingin mencobanya sendiri, saya sarankan Anda copy-paste dari blok kode di bawah ini:
# The sed part is only used as a little trick to add the
# row number as the first field in the output
sh$ sed -n = ACCOUNTNUM.csv |
paste -d'◇' - ACCOUNT*.csv | tail -5
26�MISCELLANEOUS CHARGES�657991
27�VAT BS/DEBIT�445333
28�PAYABLES�4356
29�LANDLINE TELEPHONE�626510
30�VAT BS/ENC�445452
Cukup menipu, bukan? Alih-alih berlian putih yang diharapkan, saya memiliki simbol "tanda tanya" itu (setidaknya, inilah yang ditampilkan di sistem saya). Ini bukan karakter "acak". Ini adalah karakter pengganti Unicode yang digunakan “untuk menunjukkan masalah ketika sistem tidak dapat merender aliran data ke simbol yang benar” . Jadi, apa yang salah?
Sekali lagi, memeriksa konten biner mentah dari output akan memberi kita beberapa petunjuk:
sh$ sed -n = ACCOUNTNUM.csv | paste -d'◇' - ACCOUNT*.csv | tail -5 | hexdump -C
00000000 32 36 e2 4d 49 53 43 45 4c 4c 41 4e 45 4f 55 53 |26.MISCELLANEOUS|
00000010 20 43 48 41 52 47 45 53 97 36 35 37 39 39 31 0a | CHARGES.657991.|
00000020 32 37 e2 56 41 54 20 42 53 2f 44 45 42 49 54 97 |27.VAT BS/DEBIT.|
00000030 34 34 35 33 33 33 0a 32 38 e2 50 41 59 41 42 4c |445333.28.PAYABL|
00000040 45 53 97 34 33 35 36 0a 32 39 e2 4c 41 4e 44 4c |ES.4356.29.LANDL|
00000050 49 4e 45 20 54 45 4c 45 50 48 4f 4e 45 97 36 32 |INE TELEPHONE.62|
00000060 36 35 31 30 0a 33 30 e2 56 41 54 20 42 53 2f 45 |6510.30.VAT BS/E|
00000070 4e 43 97 34 34 35 34 35 32 0a |NC.445452.|
0000007a
Kami sudah memiliki kesempatan untuk berlatih dengan hex dump di atas, jadi mata Anda sekarang harus cukup tajam untuk melihat pembatas bidang dalam aliran byte. Dengan melihat lebih dekat, Anda akan melihat pemisah bidang setelah nomor baris adalah byte e2
. Tetapi jika Anda melanjutkan penyelidikan, Anda akan melihat pemisah kolom kedua adalah 97
. Tidak hanya paste
perintah tidak menampilkan karakter yang saya inginkan, tetapi juga tidak menggunakan byte yang sama di mana-mana sebagai pemisah?!?
Tunggu sebentar:bukankah itu mengingatkan Anda akan sesuatu yang sudah kita bicarakan? Dan dua byte itu e2 97
, bukankah mereka agak akrab bagi Anda? Yah, familiar mungkin sedikit berlebihan, tetapi jika Anda melompat ke belakang beberapa paragraf, Anda mungkin menemukan mereka disebutkan di suatu tempat…
Jadi apakah Anda menemukan di mana itu? Sebelumnya, saya katakan di UTF-8, berlian putih dikodekan sebagai tiga byte e2 97 87
. Dan memang, paste
perintah telah menganggap urutan itu bukan sebagai karakter tiga byte keseluruhan, tetapi sebagai tiga independen byte dan seterusnya, byte pertama digunakan sebagai pemisah bidang pertama, kemudian byte kedua sebagai pemisah bidang kedua.
Saya membiarkan Anda menjalankan kembali eksperimen itu dengan menambahkan satu kolom lagi di data input; Anda akan melihat pemisah bidang ketiga menjadi 87
— byte ketiga dari representasi UTF-8 untuk berlian putih.
Ok, itu dia penjelasannya:paste
perintah hanya menerima "karakter" satu byte sebagai pemisah. Dan itu sangat menjengkelkan, karena, sekali lagi, saya tidak tahu cara apa pun untuk mengatasi batasan itu kecuali dengan menggunakan /dev/null
trik yang sudah saya berikan kepada Anda:
sh$ sed -n = ACCOUNTNUM.csv |
paste -d'◇' \
- /dev/null /dev/null \
ACCOUNTLIB.csv /dev/null /dev/null \
ACCOUNTNUM.csv | tail -5
26◇MISCELLANEOUS CHARGES◇657991
27◇VAT BS/DEBIT◇445333
28◇PAYABLES◇4356
29◇LANDLINE TELEPHONE◇626510
30◇VAT BS/ENC◇445452
Jika Anda membaca artikel saya sebelumnya tentang cut
perintah, Anda mungkin ingat saya memiliki masalah serupa dengan implementasi GNU alat itu. Tetapi saya perhatikan saat itu implementasi OpenBSD dengan benar mempertimbangkan LC_CTYPE
pengaturan lokal untuk mengidentifikasi karakter multibyte. Karena penasaran, saya telah menguji paste
perintah di OpenBSD juga. Sayangnya, dengan hasil yang sama seperti pada kotak Debian saya kali ini, meskipun spesifikasi untuk paste
utilitas menyebutkan variabel lingkungan LC_CTYPE sebagai menentukan ” lokal untuk interpretasi urutan byte data teks sebagai karakter (misalnya, byte tunggal sebagai lawan karakter multi-byte dalam argumen dan file input)” . Dari pengalaman saya, semua implementasi utama paste
utilitas saat ini mengabaikan karakter multi-byte dalam daftar pembatas dan menganggap pemisah satu-byte. Tetapi saya tidak akan mengklaim telah mengujinya untuk seluruh variasi platform *nix. Jadi jika saya melewatkan sesuatu di sini, jangan ragu untuk menggunakan bagian komentar untuk mengoreksi saya!
Kiat Bonus:Menghindari \0 perangkap
Untuk alasan historis:
Perintah:
tempel -d “\0” … tempel -d “” …
tidak selalu setara; yang terakhir tidak ditentukan oleh volume IEEE Std 1003.1-2001 ini dan dapat mengakibatkan kesalahan. Konstruk '\0' digunakan untuk berarti "tidak ada pemisah" karena versi historis tempel tidak mengikuti pedoman sintaks, dan perintah:
tempel -d”” …
tidak dapat ditangani dengan baik oleh getopt().
Jadi, cara portabel untuk menempelkan file tanpa menggunakan pembatas adalah dengan menentukan \0
pembatas. Ini agak berlawanan dengan intuisi karena, untuk banyak perintah, \0
berarti karakter NUL–karakter yang dikodekan sebagai byte yang dibuat hanya dari nol yang tidak boleh berbenturan dengan konten teks apa pun.
Anda mungkin menemukan karakter NUL sebagai pemisah yang berguna terutama ketika data Anda mungkin berisi karakter arbitrer (seperti saat bekerja dengan nama file atau data yang disediakan pengguna). Sayangnya, saya tidak mengetahui cara apa pun untuk menggunakan karakter NUL sebagai pembatas bidang dengan paste
memerintah. Tapi mungkin Anda tahu bagaimana melakukannya? Jika itu masalahnya, saya akan dengan senang hati membaca solusi Anda di bagian perintah.
Di sisi lain, paste
bagian implementasi dari GNU Coreutils memiliki -z
non non-standar opsi untuk beralih dari baris baru ke karakter NUL untuk pemisah baris. Namun dalam hal ini, karakter NUL akan digunakan sebagai pemisah baris keduanya untuk masukan dan keluaran. Jadi, untuk menguji fitur itu, pertama-tama kita memerlukan versi file input kita yang tidak diakhiri:
sh$ tr '\n' '\0' < ACCOUNTLIB.csv > ACCOUNTLIB.zero
sh$ tr '\n' '\0' < ACCOUNTNUM.csv > ACCOUNTNUM.zero
Untuk melihat apa yang berubah dalam prosesnya, kita dapat menggunakan hexdump
utilitas untuk memeriksa konten biner mentah dari file:
sh$ hexdump -C ACCOUNTLIB.csv | head -5
00000000 41 43 43 4f 55 4e 54 4c 49 42 0a 54 49 44 45 20 |ACCOUNTLIB.TIDE |
00000010 53 43 48 45 44 55 4c 45 0a 56 41 54 20 42 53 2f |SCHEDULE.VAT BS/|
00000020 45 4e 43 0a 50 41 59 41 42 4c 45 53 0a 41 43 43 |ENC.PAYABLES.ACC|
00000030 4f 4d 4f 44 41 54 49 4f 4e 20 47 55 49 44 45 0a |OMODATION GUIDE.|
00000040 56 41 54 20 42 53 2f 45 4e 43 0a 50 41 59 41 42 |VAT BS/ENC.PAYAB|
sh$ hexdump -C ACCOUNTLIB.zero | head -5
00000000 41 43 43 4f 55 4e 54 4c 49 42 00 54 49 44 45 20 |ACCOUNTLIB.TIDE |
00000010 53 43 48 45 44 55 4c 45 00 56 41 54 20 42 53 2f |SCHEDULE.VAT BS/|
00000020 45 4e 43 00 50 41 59 41 42 4c 45 53 00 41 43 43 |ENC.PAYABLES.ACC|
00000030 4f 4d 4f 44 41 54 49 4f 4e 20 47 55 49 44 45 00 |OMODATION GUIDE.|
00000040 56 41 54 20 42 53 2f 45 4e 43 00 50 41 59 41 42 |VAT BS/ENC.PAYAB|
Saya akan membiarkan Anda membandingkan sendiri dua hex dump di atas untuk mengidentifikasi perbedaan antara file ".zero" dan file teks asli. Sebagai petunjuk, saya dapat memberi tahu Anda bahwa baris baru dikodekan sebagai 0a
byte.
Mudah-mudahan, Anda meluangkan waktu yang diperlukan untuk menemukan karakter NUL di file input ".zero". Bagaimanapun, kami sekarang memiliki versi file input yang diakhiri dengan nol, jadi kami dapat menggunakan -z
pilihan paste
perintah untuk menangani data tersebut, menghasilkan output serta hasil nol-dihentikan:
# Hint: in the hexadecimal dump:
# the byte 00 is the NUL character
# the byte 09 is the TAB character
# Look at any ASCII table to find the mapping
# for the letters or other symbols
# (https://en.wikipedia.org/wiki/ASCII#Character_set)
sh$ paste -z *.zero | hexdump -C | head -5
00000000 41 43 43 4f 55 4e 54 4c 49 42 09 41 43 43 4f 55 |ACCOUNTLIB.ACCOU|
00000010 4e 54 4e 55 4d 00 54 49 44 45 20 53 43 48 45 44 |NTNUM.TIDE SCHED|
00000020 55 4c 45 09 36 32 33 34 37 37 00 56 41 54 20 42 |ULE.623477.VAT B|
00000030 53 2f 45 4e 43 09 34 34 35 34 35 32 00 50 41 59 |S/ENC.445452.PAY|
00000040 41 42 4c 45 53 09 34 33 35 36 00 41 43 43 4f 4d |ABLES.4356.ACCOM|
# Using the `tr` utility, we can map \0 to newline
# in order to display the output on the console:
sh$ paste -z *.zero | tr '\0' '\n' | head -3
ACCOUNTLIB ACCOUNTNUM
TIDE SCHEDULE 623477
VAT BS/ENC 445452
Karena file input saya tidak berisi baris baru yang disematkan dalam data, -z
pilihan kegunaan terbatas di sini. Namun berdasarkan penjelasan di atas, saya biarkan Anda mencoba memahami mengapa contoh berikut ini bekerja “seperti yang diharapkan”. Untuk memahami sepenuhnya bahwa Anda mungkin perlu mengunduh file sampel dan memeriksanya pada tingkat byte menggunakan hexdump
utilitas seperti yang kami lakukan di atas:
# Somehow, the head utility seems to be confused
# by the ACCOUNTS file content (I wonder why?;)
sh$ head -3 CATEGORIES ACCOUNTS
==> CATEGORIES <==
PRIVATE
ACCOMMODATION GUIDE
SHARED
==> ACCOUNTS <==
6233726230846265106159126579914356613866618193623477623795445333445452605751
# The output is quite satisfactory, putting the account number
# after the account name and keeping things surprisingly nicely formatted:
sh$ paste -z -d':' CATEGORIES ACCOUNTS | tr '\0' '\n' | head -5
PRIVATE
ACCOMMODATION GUIDE:623372
SHARED
ADVERTISEMENTS:623084
Apa lagi?
paste
perintah hanya menghasilkan keluaran teks yang dibatasi. Tetapi seperti yang diilustrasikan di akhir video pengantar, jika sistem Anda mendukung column
BSD utilitas, Anda dapat menggunakannya untuk mendapatkan tabel yang diformat dengan baik dengan mengonversi paste
output perintah ke format teks lebar tetap. Tapi itu akan menjadi subjek artikel yang akan datang. Jadi pantau terus, dan seperti biasa, jangan lupa untuk membagikan artikel itu di situs web dan media sosial favorit Anda!