Як скоротити / шлях / до / файл до / p / t / файл


9

Я шукаю елегантний однолінійний (наприклад, awk), який скоротить рядок шляху Unix, використовуючи перший символ кожного батьківського / проміжного рівня, але повне базове ім'я. Простіше показати на прикладах:

  • /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
    Зважаючи на хороші моменти @ MichaelKjörling та @ChrisH нижче, цей приклад показує, як ми можемо показати перші два символи, коли перший символ є крапкою.

Пропозиція (я не знаю ваш випадок використання): скорочуйте замість цього /f/b/.c/wizard_magic. Крапка часто настільки поширена в певному каталозі, що є дуже маленькою підказкою того, куди ви повинні шукати.
Кріс Н

Крім того, що сказав @ChrisH, .зазвичай просто означає "поточний каталог". Так /f/b/./wizard_magicце те саме, що /f/b/wizard_magicтому, що елемент шляху ./стискається до порожнього елемента шляху.
CVn

Навіщо вам це потрібно? Чи не можете ви використати якісь розумні автозавершення у своїй інтерактивній оболонці (можливо, змінивши оболонку на щось адекватне)
Basile Starynkevitch

Відповіді:


7

Для цього тестового файлу:

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

Скорочення можуть бути згенеровані за допомогою цього коду awk:

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

Edit1: Використання двох символів для імен точок

Ця версія скорочує імена каталогів до одного символу, крім імен, які починаються з .яких скорочуються до двох символів:

$ 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

Як це працює

  • -F/

    Це говорить awk використовувати косу рису як роздільник поля на вході.

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

    Це петля над кожним полем, крім останнього, і замінює його лише його першим символом.

    EDIT1: У доопрацьованій версії ми робимо довжину підрядки 2, коли поле починається з ..

  • 1

    Це сповіщає awk надрукувати переглянутий рядок.

  • OFS=/

    Це говорить awk використовувати косу рису як роздільник поля на виході.


Відмінна відповідь, незначна модифікація використання роздільника: awk -F/ '{for (i=1;i<NF;i++) $i=substr($i,1,1+($i~/^[.]/))(i==1||length($i)<2?"":"‥")} 1' OFS=/ <<<$PWDдає: /foo/bar/.config/wizard_magic/f‥/b‥/.c‥/wizard_magic
ideaman42

12

Досить просто в sed (припускаючи, що в іменах файлів немає нових рядків):

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

Менш легкий у awk, оскільки йому бракує зворотних посилань (за винятком Gawk, але з незграбним синтаксисом):

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

У zsh (з доріжкою в $full_path):

echo "${(j:/:)${(@r:1:)${(@s:/:)${full_path:h}}}}/${full_path:t}"

2
IIRC, "backreferences" - це посилання на захоплення груп, які зустрічаються в шаблоні, а не в рядку заміни.
Римоїд

@Rhymoid \1в рядку заміни це означає посилання на захоплення групи в шаблоні. Зворотний зв'язок - це зворотне відношення незалежно від того, де ви його використовуєте.
Жил "ТАК - перестань бути злим"

8

Ви можете це зробити так:

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

/u/s/m/man1

і ось 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/

це дуже близько до того, щоб виконувати всі ті ж речі, що і нижче. він не скорочується з тильдами і не вставляє на $PWDголову для провідного не-косого кута, як це робить функція (і насправді ніколи не друкує провідну косу рису), але з цим можна було б звертатися згодом. він обробляє нульові компоненти контуру, одинарні крапки та ..випадки бур’янів .

заданий той самий manшлях, cdщо і надрукований над ним:

u/s/m/man1

він також надрукує одну або дві додаткові провідні точки для кожного компонента шляху, який починається з такого, і не є лише однією або двома крапками.

ви запитували про те, як зробити більше одного символу для компонента шляху, що починається з "a" .. для цього я зрозумів, що кожен компонент так чи інакше потребує індивідуальної уваги, і тому, що мені було цікаво, я спробував свої сили в розробці канонічного шляху без каталогу змін. після деяких проб і помилок я врешті вирішив, що єдиний спосіб зробити це правильно - це зробити два рази - назад і вперед:

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:--}"
}

так що ніколи не змінюється каталог або не намагається підтвердити існування будь-якого компонента шляху, але він видавлює повторні /роздільники і повністю скидає /./одноточкові компоненти, а також обробляє /../подвійні точки.

коли $IFSвстановлено якийсь символ, який не є пробілом , послідовність двох або більше $IFSсимволів призведе до одного або декількох нульових полів. тож декілька послідовних косої риски працюють з нульовими аргументами. те саме стосується і провідного $IFSперсонажа. і тому, коли set -- $1розбивається, якщо результат $1є нульовим, тоді він починався з косою рисою, інакше, ${1:+$PWD}якщо він не є null, то я вставляю $PWD. Іншими словами, якщо перший аргумент не починається з косої риски, він буде попередньо $PWDвисунутий. це так само близько, як це стосується перевірки шляху .

в іншому випадку перший forцикл рекурсивно інвертує порядок компонентів шляху, наприклад:

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

... при цьому він ігнорує будь-які одноточкові або нульові компоненти, і для ..цього робить ...

      1 .. 3
1     .. 3
      3
3

... другий пропуск обертає цей ефект, і, роблячи це, він видавлює кожен компонент або на 2 крапки + char , або на 1 крапки + char , або на char .

тому воно повинно вийти на канонічний шлях незалежно від існування.

я додав / відніс трохи до другої петлі. тепер це setрідше (лише один раз для кожного [!./]*компонента) , і caseбільшу частину часу (завдяки вищезгаданій схемі) оцінюють схему короткого замикання і включає оцінку відповідності протидії виклику ~. якщо всі або провідна частина (поділена на цілі компоненти) остаточно канонічного шляху можуть збігатися ~, біт узгодження буде знятий і буквальний ~буде заміщений. для цього мені довелося зберегти повну копію шляху поряд із скороченою (адже відповідність скороченого шляху, ~ймовірно, не буде дуже корисною) , і тому це зберігається в $3. останнійwhileгілка циклу запускається лише у випадку, якщо ~вона відповідна як підмножина $3.

якщо ви запускаєте його з set -xвключеним слідом, ви можете спостерігати, як він працює.

$ (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

4
Класно, але мені болять очі.
glenn jackman

1
@don_crissti - так!
mikeserv

2

«Тьмяне» Zsh тема від Oh My Zsh містить Perl фрагмент коду , щоб зробити це , що має підтримку Unicode:

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

1

Ви хочете мати коротке ім'я s або використовувати його для свого командного рядка?
Для командного рядка у мене є такі пропозиції:
Чи не допоможе вам заповнення файлу у вашій оболонці?
Іноді вам пощастило і не потрібно робити щось особливе:

# /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

Якщо у вас є лише деякі каталоги, які вас цікавлять, ви можете використовувати псевдоніми:

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"

Або ви можете встановити змінні для ваших улюблених панів

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

Я думаю, що ці параметри мають більше сенсу, ніж намагатися вирішити це за допомогою функції, визначеної у .bashrc (або .profile), як

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

і виклик цієї функції x з пробілами між вашими літерами:

 # cd /path/to
 x /p t

 # cd /tmp 
 x /t

 # cd /foo/bar/.config
 x /f b 
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.