Cara menyingkat / path / ke / file ke / p / t / file

9

Saya mencari satu-liner elegan (misalnya, awk) yang akan mempersingkat string jalur Unix menggunakan karakter pertama dari setiap orang tua / tingkat menengah, tetapi nama lengkap penuh. Lebih mudah ditunjukkan dengan contoh:

  • /path/to/file/p/t/file
  • /tmp/tmp
  • /foo/bar/.config/wizard_magic/f/b/./wizard_magic
  • /foo/bar/.config/wizard_magic/f/b/.c/wizard_magic
    Mengingat poin-poin bagus oleh @ MichaelKjörling dan @ChrisH di bawah, contoh ini menunjukkan bagaimana kita dapat menampilkan dua karakter pertama ketika karakter pertama adalah sebuah titik.
Joshua Huber
sumber
Saran (saya tidak tahu kasus penggunaan Anda): disingkat menjadi /f/b/.c/wizard_magic. Titik sering sangat umum dalam direktori tertentu untuk menjadi petunjuk yang sangat kecil ke tempat Anda harus mencari.
Chris H
Selain apa yang dikatakan @ChrisH, .biasanya hanya berarti "direktori saat ini". Jadi /f/b/./wizard_magicsama dengan /f/b/wizard_magickarena elemen path ./dikompres ke elemen path kosong.
CVn
Mengapa Anda membutuhkannya? Tidak bisakah Anda menggunakan pelengkapan otomatis pintar di shell interaktif Anda (mungkin mengubah shell Anda menjadi sesuatu yang memadai)
Basile Starynkevitch

Jawaban:

7

Untuk file uji ini:

$ cat path
/path/to/file
/tmp
/foo/bar/.config/wizard_magic

Singkatan dapat dihasilkan dengan kode awk ini:

$ awk -F/ '{for (i=1;i<NF;i++) $i=substr($i,1,1)} 1' OFS=/ path
/p/t/file
/tmp
/f/b/./wizard_magic

Sunting1: Menggunakan dua karakter untuk nama-titik

Versi ini menyingkat nama direktori menjadi satu karakter kecuali untuk nama yang dimulai dengan .yang disingkat menjadi dua karakter:

$ awk -F/ '{for (i=1;i<NF;i++) $i=substr($i,1,1+($i~/^[.]/))} 1' OFS=/ path
/p/t/file
/tmp
/f/b/.c/wizard_magic

Bagaimana itu bekerja

  • -F/

    Ini memberitahu awk untuk menggunakan garis miring sebagai pemisah bidang pada input.

  • for (i=1;i<NF;i++) $i=substr($i,1,1)

    Ini melingkupi setiap bidang, kecuali yang terakhir, dan menggantinya hanya dengan karakter pertamanya.

    EDIT1: Dalam versi revisi, kami membuat panjang substring 2 ketika bidang dimulai ..

  • 1

    Ini memberitahu awk untuk mencetak baris yang direvisi.

  • OFS=/

    Ini memberitahu awk untuk menggunakan garis miring sebagai pemisah bidang pada keluaran.

John1024
sumber
Jawaban luar biasa, modifikasi kecil untuk menggunakan pemisah: awk -F/ '{for (i=1;i<NF;i++) $i=substr($i,1,1+($i~/^[.]/))(i==1||length($i)<2?"":"‥")} 1' OFS=/ <<<$PWDmemberikan: /foo/bar/.config/wizard_magic/f‥/b‥/.c‥/wizard_magic
ideasman42
12

Cukup mudah di sed (dengan asumsi tidak ada baris baru dalam nama file):

sed 's!\([^/]\)[^/]*/!\1/!g'

Kurang mudah dalam awk karena tidak memiliki referensi kembali (kecuali dalam Gawk, tetapi dengan sintaks yang canggung):

awk -v FS=/ -v OFS=/ '{for (i=1; i<NF; i++) $i=substr($i,1,1)} 1'

Di zsh (dengan jalur di $full_path):

echo "${(j:/:)${(@r:1:)${(@s:/:)${full_path:h}}}}/${full_path:t}"
Gilles 'SANGAT berhenti menjadi jahat'
sumber
2
IIRC, "backreferences" adalah referensi untuk menangkap kelompok yang muncul dalam pola, bukan pada string pengganti.
Rhymoid
@Rhymoid \1dalam string pengganti tidak berarti referensi ke grup tangkapan dalam pola. Backreference adalah backreference di mana pun Anda menggunakannya.
Gilles 'SANGAT berhenti menjadi jahat'
8

Anda dapat melakukannya seperti:

cd /usr///.//share/../share//man/man1 || exit
IFS=/; set -f
printf %.1s/  ${PWD%/*}
printf %s\\n "${PWD##*/}"

/u/s/m/man1

dan inilah sed:

printf %s "$file" |
tr /\\n \\n/      | sed -et$ \
    -e '\|^\.\.$|{x;s|\(.*\)\n.*$|\1|;x;}'  \
    -e 's|^\.\{0,2\}$||;\|.|H;$!d;x'        \
-e$ -e '\|\(\.\{0,2\}.\)\(.*\)\(\n\)|!b'    \
    -e 's||\1\3\2\3|;P;s|\n||;D' |
tr /\\n \\n/

yang cukup dekat untuk melakukan semua hal yang sama fungsi tidak di bawah ini. itu tidak menyingkat dengan tildes atau memasukkan $PWDke kepala untuk non-slash terkemuka seperti fungsinya (dan pada kenyataannya, tidak pernah mencetak slash utama) tetapi itu bisa ditangani sesudahnya. itu memproses komponen jalur nol, dan titik tunggal, dan menyingkirkan ..kasus.

diberi manjalur yang sama seperti di cdatas yang dicetak:

u/s/m/man1

itu juga akan mencetak satu atau dua titik utama tambahan untuk setiap komponen jalur yang dimulai dengan itu dan bukan hanya satu atau dua titik.

Anda bertanya tentang melakukan lebih dari satu karakter untuk path-komponen yang dimulai dengan a .. untuk melakukannya saya pikir setiap komponen akan memerlukan perhatian individu, dan karena saya ingin tahu, saya mencoba tangan saya untuk membuat jalur kanonik tanpa direktori perubahan. setelah beberapa trial-and-error saya akhirnya memutuskan satu-satunya cara untuk melakukannya dengan benar adalah melakukannya dua kali - mundur dan maju:

pathbytes(){
    local IFS=/   o="$-" p
    set -f${ZSH_VERSION+LFy}
    set -- ${1:-$PWD}
    for p   in      /${1:+$PWD} $*
    do      case    $p in   (.|"")  ;;
            (..)    ${1+shift}      ;;
            (/)     set --          ;;
            (*)     set -- $p $*;   esac
    done
    for p   in      //$* ""
    do      case   ${p:-/$3}        in
            ([!./]*)                ;;
            (..*)   set "..$@"      ;;
            (.*)    set ".$@"       ;;
            (//*) ! set "" $1 $1    ;;
            (~)   ! p=\~            ;;
            (~/*)   p="~/$2";set $HOME
                  ! while "${2+shift}" 2>&3
                    do   p="~/${p#??*/}"
                    done 3>/dev/null;;
            esac&&  set ""  "${p%"${p#$1?}"}/$2" "$p/$3"
    done;   printf %s\\n "${p:-$2}"
    set +f  "-${o:--}"
}

sehingga tidak pernah mengubah direktori atau mencoba mengonfirmasi keberadaan komponen jalur apa pun, tetapi meremas /pembatas berulang dan menjatuhkan /./komponen titik tunggal sepenuhnya, dan memproses /../komponen titik ganda secara tepat.

ketika $IFSdiatur ke beberapa karakter non-spasi , urutan dua atau lebih $IFSkarakter akan menghasilkan satu atau lebih bidang nol. jadi beberapa tebasan berurutan berhasil pada argumen bernilai nol. hal yang sama berlaku untuk $IFStokoh utama . dan ketika set -- $1terbelah, jika hasilnya $1nol maka dimulai dengan garis miring, ${1:+$PWD}jika tidak, maka saya masukkan $PWD. dengan kata lain, jika argumen pertama tidak dimulai dengan garis miring, itu akan $PWDdipertanyakan. itu sedekat ini dengan validasi jalur .

jika tidak, forloop pertama secara terbalik membalikkan urutan komponen path, seperti:

      1 2 3
1     2 3
2 1   3
3 2 1

... saat melakukan itu mengabaikan komponen titik-tunggal atau nol, dan untuk ..itu tidak ...

      1 .. 3
1     .. 3
      3
3

... pass kedua membalikkan efek ini, dan saat melakukannya ia memeras setiap komponen menjadi 2-titik + char , atau 1-titik + char , atau char .

jadi itu harus bekerja ke jalur kanonik terlepas dari keberadaannya.

saya menambahkan / mengurangi sedikit ke loop kedua. sekarang setlebih jarang (hanya sekali untuk setiap [!./]*komponen) , dan caseevaluasi pola hubung singkat sebagian besar waktu (berkat pola yang disebutkan di atas) , dan termasuk evaluasi kecocokan ekor-panggilan terhadap ~. jika semua atau bagian terdepan (sebagaimana dibagi pada seluruh komponen) dari jalur kanonik akhirnya dapat cocok ~, bit yang cocok akan dilepas dan literal ~akan diganti. untuk melakukan ini, saya harus memelihara salinan lengkap dari path disamping yang disingkat juga (karena mencocokkan path yang disingkat dengan ~mungkin tidak akan sangat membantu) , dan ini disimpan $3. yang terakhirwhilecabang loop hanya dijalankan jika ~dicocokkan sebagai subset dari $3.

jika Anda menjalankannya dengan set -xjejak diaktifkan, Anda dapat melihatnya bekerja.

$ (set -x;pathbytes ..abc/def/123///././//.././../.xzy/mno)
+ pathbytes ..abc/def/123///././//.././../.xzy/mno
+ local IFS=/ o=xsmi p
+ set -f
+ set -- ..abc def 123   . .   .. . .. .xzy mno
+ set --
+ set -- home
+ set -- mikeserv home
+ set -- ..abc mikeserv home
+ set -- def ..abc mikeserv home
+ set -- 123 def ..abc mikeserv home
+ shift
+ shift
+ set -- .xzy ..abc mikeserv home
+ set -- mno .xzy ..abc mikeserv home
+ set  mno mno
+ set . mno mno
+ set  .x/mno .xzy/mno
+ set .. .x/mno .xzy/mno
+ set  ..a/.x/mno ..abc/.xzy/mno
+ set  m/..a/.x/mno mikeserv/..abc/.xzy/mno
+ set  h/m/..a/.x/mno home/mikeserv/..abc/.xzy/mno
+ p=~/h/m/..a/.x/mno
+ set  home mikeserv
+ shift
+ p=~/m/..a/.x/mno
+ shift
+ p=~/..a/.x/mno
+
+ printf %s\n ~/..a/.x/mno
~/..a/.x/mno
+ set +f -xsmi
mikeserv
sumber
4
Keren, tapi mataku sakit.
glenn jackman
1
@don_crissti - ya!
mikeserv
2

Tema "mencurigakan" Zsh dari Oh My Zsh berisi cuplikan Perl untuk melakukan hal yang memiliki dukungan Unicode:

perl -pe '
   BEGIN {
      binmode STDIN,  ":encoding(UTF-8)";
      binmode STDOUT, ":encoding(UTF-8)";
   }; s|^$HOME|~|g; s|/([^/.])[^/]*(?=/)|/$1|g; s|/\.([^/])[^/]*(?=/)|/.$1|g;
'
nwk
sumber
1

Apakah Anda ingin memiliki nama pendek atau menggunakannya untuk commandline Anda?
Untuk baris perintah, saya memiliki saran berikut:
Tidak menyelesaikan file di shell Anda membantu Anda?
Terkadang Anda beruntung dan tidak harus melakukan sesuatu yang istimewa:

# /path/to/file -> /p/t/file
ls -l /*/*/file 

# /tmp -> /tmp
cd /tmp

# /foo/bar/.config/wizard_magic -> /f/b/./wizard_magic
ls -l /*/*/*/wizard_magic -> /f/b/./wizard_magic

Ketika Anda hanya memiliki beberapa direktori yang Anda minati, Anda dapat menggunakan alias:

alias cdto="cd /path/to"
alias cdtmp="cd /tmp"
alias cdcfg="cd /foo/bar/.config"
alias cddeep="cd /home/john/workdir/project1/version3/maven/x/y/z/and/more"

Atau Anda dapat mengatur variabel untuk direktori favorit Anda

export p="/path/to"
export f="/foo/bar/.config"
ls -l $p/file
ls -l $f/wizard_magic

Saya pikir opsi ini lebih masuk akal daripada mencoba menyelesaikan ini dengan fungsi yang didefinisikan dalam .bashrc (atau .profile) seperti

function x { 
   xxpath=""
   while [ $# -ne 0 ]; do
     xxpath+="${1}*/"
     shift
   done
   cd $(echo "${xxpath}")
}

dan memanggil fungsi ini x dengan spasi di antara huruf-huruf Anda:

 # cd /path/to
 x /p t

 # cd /tmp 
 x /t

 # cd /foo/bar/.config
 x /f b 
Walter A
sumber