Saya mencoba untuk menulis fungsi bash shell yang akan memungkinkan saya untuk menghapus duplikat direktori dari variabel lingkungan PATH saya.
Saya diberitahu bahwa adalah mungkin untuk mencapai ini dengan satu perintah baris menggunakan awk
perintah, tetapi saya tidak tahu bagaimana melakukannya. Adakah yang tahu bagaimana caranya?
Jawaban:
Jika Anda belum memiliki duplikat di
PATH
dan Anda hanya ingin menambahkan direktori jika belum ada di sana, Anda dapat melakukannya dengan mudah dengan shell saja.Dan inilah cuplikan shell yang menghilangkan duplikat dari
$PATH
. Itu berjalan melalui entri satu per satu, dan menyalin yang belum terlihat.sumber
PATH=$PATH:x=b
x dalam PATH asli mungkin memiliki nilai a, jadi ketika iterate dalam urutan, maka nilai baru akan diabaikan, tetapi ketika dalam urutan terbalik, yang baru nilai akan berlaku.PATH=x:$PATH
.PATH=$PATH:...
tidakPATH=...:$PATH
. Oleh karena itu lebih tepat untuk mengulangi urutan terbalik. Meskipun cara Anda juga akan berhasil, maka orang menambahkan dengan cara sebaliknya.Berikut ini adalah solusi satu-liner yang dapat dipahami yang melakukan semua hal yang benar: menghapus duplikat, mempertahankan urutan jalur, dan tidak menambahkan titik dua pada akhirnya. Jadi itu akan memberi Anda PATH deduplicated yang memberikan perilaku yang sama persis seperti aslinya:
Itu hanya terbagi pada titik dua (
split(/:/, $ENV{PATH})
), menggunakan kegunaangrep { not $seen{$_}++ }
untuk menyaring setiap contoh jalur kecuali untuk kejadian pertama, dan kemudian bergabung dengan yang tersisa kembali bersama-sama dipisahkan oleh titik dua dan mencetak hasilnya (print join(":", ...)
).Jika Anda ingin beberapa struktur di sekitarnya, serta kemampuan untuk mendupuplikasi variabel lain juga, coba cuplikan ini, yang saat ini saya gunakan dalam konfigurasi saya sendiri:
Kode itu akan mendupuplikasi PATH dan MANPATH, dan Anda dapat dengan mudah memanggil
dedup_pathvar
variabel lain yang menyimpan daftar jalur yang dipisahkan oleh titik dua (misalnya PYTHONPATH).sumber
chomp
untuk menghapus baris baru. Ini bekerja untuk saya:perl -ne 'chomp; print join(":", grep { !$seen{$_}++ } split(/:/))' <<<"$PATH"
Inilah yang ramping:
Lebih lama (untuk melihat cara kerjanya):
Oke, karena Anda baru mengenal linux, berikut adalah cara untuk benar-benar mengatur PATH tanpa trailing ":"
btw pastikan untuk TIDAK memiliki direktori yang berisi ":" di PATH Anda, jika tidak maka akan kacau.
beberapa kredit untuk:
sumber
echo -n
. Perintah Anda tampaknya tidak berfungsi dengan "di sini string" misalnya coba:awk -v RS=: -v ORS=: '!arr[$0]++' <<< ".:/foo/bin:/bar/bin:/foo/bin"
Ini adalah AWK one liner.
dimana:
printf %s "$PATH"
mencetak konten$PATH
tanpa baris baruRS=:
mengubah karakter pembatas rekaman input (standarnya adalah baris baru)ORS=
mengubah pembatas catatan keluaran ke string kosonga
nama array yang dibuat secara implisit$0
referensi catatan saat inia[$0]
adalah dereference array asosiatif++
adalah operator pasca kenaikan!a[$0]++
menjaga sisi kanan, yaitu memastikan bahwa catatan saat ini hanya dicetak, jika tidak dicetak sebelumnyaNR
nomor rekaman saat ini, dimulai dengan 1Itu berarti AWK digunakan untuk membagi
PATH
konten di sepanjang:
karakter pembatas dan untuk menyaring entri duplikat tanpa mengubah urutan.Karena array asosiatif AWK diimplementasikan sebagai tabel hash runtime adalah linear (yaitu dalam O (n)).
Perhatikan bahwa kita tidak perlu mencari
:
karakter yang dikutip karena shell tidak memberikan penawaran untuk mendukung direktori dengan:
namanya dalamPATH
variabel.Awk + rekat
Di atas dapat disederhanakan dengan menempelkan:
The
paste
Perintah ini digunakan untuk menyelingi output awk dengan titik dua. Ini menyederhanakan tindakan awk untuk mencetak (yang merupakan tindakan standar).Python
Sama dengan Python two-liner:
sumber
paste
perintah tidak bekerja untuk saya kecuali saya menambahkan trailing-
untuk menggunakan STDIN.-v
atau saya mendapatkan kesalahan.-v RS=: -v ORS=
. Hanya berbeda rasa dariawk
sintaks.Telah ada diskusi serupa tentang ini di sini .
Saya mengambil sedikit pendekatan yang berbeda. Daripada hanya menerima PATH yang diatur dari semua file inisialisasi berbeda yang diinstal, saya lebih suka menggunakan
getconf
untuk mengidentifikasi jalur sistem dan menempatkannya terlebih dahulu, kemudian menambahkan urutan jalur pilihan saya, kemudian gunakanawk
untuk menghapus duplikat. Ini mungkin atau mungkin tidak benar-benar mempercepat eksekusi perintah (dan secara teori lebih aman), tetapi ini memberi saya fuzzies hangat.sumber
:
kePATH
(yaitu entri string kosong), karena direktori kerja saat ini adalah bagian dari AndaPATH
.Selama kami menambahkan oneliners non-awk:
(Bisa sesederhana
PATH=$(zsh -fc 'typeset -U path; echo $PATH')
tapi zsh selalu membaca setidaknya satuzshenv
file konfigurasi, yang dapat dimodifikasiPATH
.)Ini menggunakan dua fitur zsh yang bagus:
typeset -T
)typeset -U
).sumber
Ini menggunakan perl dan memiliki beberapa manfaat:
/usr/bin:/sbin:/usr/bin
akan menghasilkan/usr/bin:/sbin
)sumber
Juga
sed
(di sini menggunakansed
sintaksis GNU ) dapat melakukan pekerjaan:yang ini hanya berfungsi dengan baik jika jalur pertama adalah
.
seperti dalam contoh dogbane.Secara umum, Anda perlu menambahkan
s
perintah lain :Ia bekerja bahkan pada konstruksi seperti itu:
sumber
Seperti orang lain telah menunjukkan itu mungkin dalam satu baris menggunakan awk, sed, perl, zsh, atau bash, tergantung pada toleransi Anda untuk garis panjang dan keterbacaan. Inilah fungsi bash itu
fungsi bash
pemakaian
Untuk menghapus dups dari PATH
sumber
Ini versi saya:
Pemakaian:
path_no_dup "$PATH"
Output sampel:
sumber
Versi bash terbaru (> = 4) juga array asosiatif, yaitu Anda juga dapat menggunakan bash 'one liner' untuknya:
dimana:
IFS
mengubah pemisah bidang input ke:
declare -A
mendeklarasikan array asosiatif${a[$i]+_}
adalah arti perluasan parameter:_
disubstitusi jika dan hanya jikaa[$i]
diatur. Ini mirip dengan${parameter:+word}
yang juga menguji tidak-nol. Jadi, dalam evaluasi kondisional berikut, ekspresi_
(yaitu string karakter tunggal) mengevaluasi ke true (ini setara dengan-n _
) - sedangkan ekspresi kosong mengevaluasi ke false.sumber
${a[$i]+_}
dengan mengedit jawaban Anda dan menambahkan satu butir. Sisanya sangat bisa dimengerti tetapi Anda kehilangan saya di sana. Terima kasih.Penjelasan kode awk:
Selain singkat, one-liner ini cepat: awk menggunakan tabel hash chaining untuk mencapai kinerja O (1) yang diamortisasi.
berdasarkan pada Menghapus entri $ PATH duplikat
sumber
if ( !x[$i]++ )
. Terima kasih.Gunakan
awk
untuk membelah jalur:
, kemudian lewati setiap bidang dan simpan dalam larik. Jika Anda menemukan bidang yang sudah ada dalam array, itu berarti Anda telah melihatnya sebelumnya, jadi jangan cetak.Berikut ini sebuah contoh:
(Diperbarui untuk menghapus trailing
:
.)sumber
Sebuah solusi - bukan solusi yang elegan seperti yang mengubah variabel * RS, tapi mungkin cukup jelas:
Seluruh program bekerja di blok BEGIN dan END . Ini menarik variabel PATH Anda dari lingkungan, membaginya menjadi beberapa unit. Ini kemudian beralih di atas p array yang dihasilkan (yang dibuat dalam urutan oleh
split()
). Array e adalah array asosiatif yang digunakan untuk menentukan apakah kita telah melihat elemen path saat ini (misalnya / usr / local / bin ) sebelumnya, dan jika tidak, ditambahkan ke np , dengan logika untuk menambahkan titik dua ke np jika sudah ada teks di np . The END blok hanya echos np . Ini dapat lebih disederhanakan dengan menambahkan-F:
menandai, menghapus argumen ketiga kesplit()
(karena default ke FS ), dan mengubahnp = np ":"
kenp = np FS
, memberi kita:Naif, saya percaya itu
for(element in array)
akan menjaga ketertiban, tetapi tidak, jadi solusi asli saya tidak bekerja, karena orang-orang akan marah jika seseorang tiba-tiba mengacak urutan mereka$PATH
:sumber
Hanya kejadian pertama yang dipertahankan dan keteraturan relatif dipertahankan.
sumber
Saya akan melakukannya hanya dengan alat dasar seperti tr, sortir dan uniq:
Jika tidak ada yang istimewa atau aneh di jalan Anda, itu harus bekerja
sumber
sort -u
bukansort | uniq
.