Как сократить / путь / к / файл к / р / т / файл

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что и элемент path ./сжимается в пустой элемент path.
CVN
Зачем тебе это надо? Разве вы не можете использовать какое-нибудь умное автозаполнение в своей интерактивной оболочке (возможно, изменив свою оболочку на что-то адекватное)
Василий Старынкевич

Ответы:

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 использовать косую черту в качестве разделителя полей на выходе.

John1024
источник
Отличный ответ, небольшая модификация для использования разделителя: 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
ideasman42
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'

В зш (с путем в $full_path):

echo "${(j:/:)${(@r:1:)${(@s:/:)${full_path:h}}}}/${full_path:t}"
Жиль "ТАК - прекрати быть злым"
источник
2
IIRC, «обратные ссылки» - это ссылки на группы захвата, которые встречаются в шаблоне, а не в строке замены.
Рифмоид
@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

он также напечатает одну или две дополнительные начальные точки для каждого компонента пути, который начинается с такового, а не просто одну или две точки.

Вы спрашивали о том, чтобы сделать больше, чем один символ для компонента пути, начинающегося с .. чтобы сделать это, я решил, что каждый компонент в любом случае будет нуждаться в индивидуальном внимании, и, поскольку мне было любопытно, я попробовал свои силы в разработке канонического пути без каталога изменений. после некоторой проб и ошибок я в итоге решил, что единственный способ сделать это правильно - это сделать это дважды - вперед и назад:

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}если оно не равно нулю, то я вставляю $PWD. другими словами, если первый аргумент не начинается с косой черты, он будет $PWDдобавлен. это настолько близко, насколько это касается проверки пути .

в противном случае первый forцикл рекурсивно инвертирует порядок компонентов пути, например:

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

... при этом он игнорирует любые одноточечные или нулевые компоненты, и для ..этого делает ...

      1 .. 3
1     .. 3
      3
3

... второй проход отменяет этот эффект, и при этом он сжимает каждый компонент либо в 2 точки + символ , либо в 1 точку + символ , либо в символ .

поэтому он должен идти по каноническому пути независимо от существования.

Я добавил / вычитал немного во второй цикл. теперь это происходит 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
mikeserv
источник
4
Круто, но глаза болят.
Гленн Джекман
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;
'
NWK
источник
1

Вы хотите иметь короткое имя или использовать его для командной строки?
Для командной строки у меня есть следующие предложения:
Разве завершение файла в вашей оболочке не поможет?
Иногда вам везет и не нужно делать что-то особенное

# /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 
Вальтер А
источник