Разложение спецификаций пути в самый длинный общий префикс + суффикс

На самом деле, for i in *; do something; done рассматривает каждое имя файла правильно, за исключением того, что имена файлов, которые начинаются с a . исключены из подстановочного соответствия. Соответствовать всем файлам (кроме . и ..) портативно, соответствие * .[!.]* ..?* и пропустите любой несуществующий файл, следующий из шаблона несоответствия, оставляемого неповрежденным.

При испытании проблем это, вероятно, потому что Вы не заключили в кавычки $i правильно позже. Всегда помещайте двойные кавычки вокруг подстановок переменных и управляйте заменами: "$foo", "$(cmd)" если Вы не предназначаете полевое разделение и globbing для случая.

Если необходимо передать имя файла внешней команде (Вы не делаете, здесь), осторожны это echo "$foo" не всегда печатает $foo буквально. Несколько оболочек выполняют расширение обратной косой черты и несколько значений $foo начало - будет рассматриваться как опцию. Безопасный и совместимый POSIX способ распечатать строку точно

printf '%s' "$foo"

или printf '%s\n' "$foo" добавить новую строку в конце. Другая вещь не упустить состоит в том, что замена команды удаляет запаздывание новых строк; если необходимо сохранить новые строки, возможный прием должен добавить несимвол новой строки к данным, удостоверьтесь, что преобразование сохраняет этот символ, и наконец усеките этот символ. Например:

mangled_file_name="$(printf '%sa' "$file_name" | tr -sc '[:alnum:]-+_.' '[_*]')"
mangled_file_name="${mangled_file_name%a}"

Для извлечения md5sum файла постарайтесь не иметь имя файла в md5sum вывод, так как это будет мешать разделять. Передайте данные md5sumстандартный вход.

Обратите внимание что md5sum команда не находится в POSIX. Несколько вариантов Unix имеют md5 или ничто вообще. cksum POSIX, но склонный к коллизии.

Посмотрите Захват расширения в имени файла о том, как получить расширение файла.

Давайте соединим все это (непротестированное). Все здесь работает под любой оболочкой POSIX; Вы могли получить немного, но не очень, от функций удара.

for old_name in * .[!.]* ..?*; do
  if ! [ -e "$old_name" ]; then continue; fi
  hash=$(md5sum <"$old_name")
  case "$old_name" in
    *.*.gz|*.*.bz2)                   # double extension
      ext=".${old_name##*.}"
      tmp="${old_name%.*}"
      ext=".${old_name##*.}$ext";;
    ?*.*) ext=".${old_name##*.}";;    # simple extension
    *) ext=;;                         # no extension
  esac
  mv -- "$old_name" "$hash$ext"
done

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

4
06.03.2013, 20:01
5 ответов

Можно сделать это в цикле оболочки. Код ниже должен работать со всеми видами странных путей с дополнительными наклонными чертами; если все Ваши пути имеют форму /foo/bar, можно сойти с рук что-то более простое.

split_common_prefix () {
  path1=$1
  path2=$2
  common_prefix=
  ## Handle initial // specially
  case $path1 in
    //[!/]*) case $path2 in
               //[!/]*) common_prefix=/ path1=${path1#/} path2=${path2#/};;
               *) return;;
             esac;;
    /*) case $path2 in
          /*) :;;
          *) return;;
        esac;;
    *) case $path2 in /*) return;; esac;;
  esac
  ## Normalize multiple slashes
  trailing_slash1= trailing_slash2=
  case $path1 in */) trailing_slash1=/;; esac
  case $path2 in */) trailing_slash2=/;; esac
  path1=$(printf %s/ "$path1" | tr -s / /)
  path2=$(printf %s/ "$path2" | tr -s / /)
  if [ -z "$trailing_slash1" ]; then path1=${path1%/}; fi
  if [ -z "$trailing_slash2" ]; then path2=${path2%/}; fi
  ## Handle the complete prefix case (faster, necessary for equality and
  ## for some cases with trailing slashes)
  case $path1 in
    "$path2")
      common_prefix=$path1; path1= path2=
      return;;
    "$path2"/*)
      common_prefix=$path2; path1=${path1#$common_prefix} path2=
      return;;
  esac
  case $path2 in
    "$path1"/*)
      common_prefix=$path1; path1= path2=${path2#$common_prefix}
      return;;
  esac
  ## Handle the generic case
  while prefix1=${path1%%/*} prefix2=${path2%%/*}
        [ "$prefix1" = "$prefix2" ]
  do
    common_prefix=$common_prefix$prefix1/
    path1=${path1#$prefix1/} path2=${path2#$prefix1/}
  done
}

С другой стороны, определите самый длинный общий префикс двух строк и обрежьте его к его последнему / символ (кроме тех случаев, когда общий префикс состоит только из наклонных черт).

1
27.01.2020, 20:47
  • 1
    @StephaneChazelas я теперь вставил копией последний код, в котором я также зафиксировал случай равенства (ой). Я думаю, что также зафиксировал случаи с запаздывающими наклонными чертами, но я не протестировал его очень. –  Gilles 'SO- stop being evil' 07.03.2013, 15:42
  • 2
    Ваш код все еще имеет незначительные проблемы. Один случай "/" и для пути и для других имен файлов проблем, содержащих пробелы. Последний является плохой практикой, но происходит с именами, сгенерированными инструментами от внешних данных. Я думаю, что Вам и @StephaneChazelas может быть интересно или удивлены простым решением, которое я добавил к ответам. –  babou 28.05.2013, 15:37
  • 3
    @babou я добираюсь / как общий префикс / и /, какова проблема? И какова проблема с пробелами? –  Gilles 'SO- stop being evil' 28.05.2013, 15:42
  • 4
    Ой... извините, моя ошибка. Я не знаю, как Вы тестируете код, который Вы представляете. Я инкапсулировал его в сценарии удара, и я был тем для упущения кавычек вокруг аргументов. Но я все еще не получаю ответа с / и /. Уверенный –  babou 28.05.2013, 17:02
  • 5
    @babou $ split_common_prefix / /; echo $?; echo "$common_prefix"0 / –  Gilles 'SO- stop being evil' 28.05.2013, 20:14

Можно вычислить самую длинную общую ведущую подстроку списка строк с этим:

sed -e '1{h;d;}' -e 'G;s,\(.*\).*\n\1.*,\1,;h;$!d'

Который, например, для:

/abc/bcd/cdf
/abc/bcd/cdf/foo
/abc/bcd/chi/hij
/abc/bcd/cdd

возвраты:

/abc/bcd/c

Ограничить его компонентами контура:

sed -e 's,$,/,;1{h;d;}' -e 'G;s,\(.*/\).*\n\1.*,\1,;h;$!d;s,/$,,'

(возвраты /abc/bcd на образце выше).

9
27.01.2020, 20:47
  • 1
    Вы завоевываете приз за самое короткое решение. Но о господи, я не понимаю это. –  ACK_stoverflow 04.02.2015, 01:50

Нет такого инструмента к моему знанию. Однако можно легко записать такую программу, так как необходимо сделать, определяют самую длинную группу компонентов.

Пример "острота":

echo /abc/bcd/cdf | awk -vpath=/abc/bcd/chi/hij -F/ '{ OFS="\n";len=0; split(path, components); for (i=1; i<=NF; i++) if($i == components[i])len+=1+length($i);else break;print substr($0, 1, len - 1), substr($0, len + 1), substr(path, len + 1);exit;}

Отформатированная версия с комментариями:

$ cat longest-path.awk
#!/usr/bin/awk -f
BEGIN {
    FS="/";   # split by slash
}
{
    len=0;                      # initially the longest path has length 1
    split(path, components);    # split by directory separator (slash)
    for (i=1; i<=NF; i++) {     # loop through all path components
        if ($i == components[i]) {
            len += 1 + length($i);
        } else {
            break;              # if there is a mismatch, terminate
        }
    }
    print substr($0, 1, len - 1);  # longest prefix minus slash
    print substr($0, len + 1);     # remainder stdin
    print substr(path, len + 1);   # remainder path
    exit;                          # only the first line is compared
}
$ echo  /abc/bcd/cdf | ./longest-path.awk -vpath=/abc/bcd/chi/hij
/abc/bcd
cdf
chi/hij
2
27.01.2020, 20:47

Вот недоброкачественная продукция, которая, кажется, отвечает на вопрос, хорошо используя обычный (стандарт?) средства unix/linux согласно просьбе (хорошо... Я попробовал его только на моем Linux Mageia).

#!/bin/sh
# Compute absolute pathnames common prefix and decompose second one
# Author Babou 2013/05/27 on http://unix.stackexchange.com/questions/67078/
first=`realpath -ms "$1"`
rel=`realpath -ms --relative-to="$1" "$2" | rev`
while [ `basename "$rel"` == '..' ]
do
   first=`dirname "$first"`
   rel=`dirname "$rel"`
done
echo $first + `echo $rel | rev`

И мой набор тестов:

./prefix /abc/bcd/cdf /abc/bcd/chi/hij
./prefix "/abc/bcd/cdf" "/abc/bcd/chi/hij"
./prefix "/ab c/bcd/cdf" "/ab c/bcd/chi/hij"
./prefix "/abc/bcd/cdf" "/abc/bcd/chi/h ij"
./prefix "/" "/"
./prefix "/abc/bcd/" "/abc/bcd/chi/hij"
./prefix "/abc/bcd/cdf" "/abc/bcd/"
./prefix "/abc///zzz/../bcd/cdf" "///abc/bcd//chi/h i j/"
./prefix "/abèc/bcd/cdf" "/abèc/bcd/"

два примера:

$ ./prefix "/abc///zzz/../bcd/cdf" "///abc/bcd//chi/h i j/"
/abc/bcd + chi/h i j
$ ./prefix "/abèc/bcd/cdf" "/abèc/bcd/"
/abèc/bcd + .

Если Вы требуете анализировать оба пути, можно или изменить сценарий или применить его дважды, изменив порядок или аргументы.

Я не слишком доволен именами переменных..., но мой первый плохой класс в программировании происходил из-за неудавшегося альфа-преобразования (одно возникновение, о котором забывают). Таким образом, я оставляю его, как.

P.S. Можно хотеть объединить презентацию относительного пути (вторая часть разложения) когда пустой: это может стать или как "/" в одном случае, когда оба пути просто "/".

0
27.01.2020, 20:47
  • 1
    realpath ни в коем случае не стандартная утилита. Версия Debian/Ubuntu только втянута как зависимость несколькими пакетами и не имеет a -m опция. Утилита GNU для канонизации путей readlink, который также существует (с меньшим количеством опций) на *BSD. –  Gilles 'SO- stop being evil' 28.05.2013, 15:48
  • 2
    AFAIK realpath утилита GNU с авторским правом FSF, и это действительно имеет a -m опция. Но Ваш комментарий помог мне заметить, что я забыл другую опцию, -s, предотвратить расширение символьной ссылки. Без -s опция, это делает то же задание на канонических путях. –  babou 28.05.2013, 22:32
  • 3
    О, это - недавнее дополнение. До недавнего времени, realpath была сторонняя утилита (или скорее было несколько утилит с тем именем), с меньшим количеством опций. –  Gilles 'SO- stop being evil' 28.05.2013, 22:37
  • 4
    Из страницы справочника на моей машине: GNU coreutils 8.15 January 2012 REALPATH(1) –  babou 28.05.2013, 22:48

Stéphane Chazelas уже показал решение, основанное на sed -. Я наткнулся на немного другое выражение sed от ack , которое я настраиваю ниже, чтобы ответить на этот вопрос. В частности, я ограничиваю его компонентами пути и обрабатываю возможность новых строк в компонентах пути. Затем я демонстрирую его использование для разложения спецификаций пути на самые длинные общие ведущие компоненты пути + оставшиеся компоненты пути .

Начнем с выражения ack для sed(Я переключил его на синтаксис ERE ):

sed -E '$!{N;s/^(.*).*\n\1.*$/\1\n\1/;D;}' <<"EOF'
/abc/bcd/cdf
/abc/bcd/cdf/foo
/abc/bcd/chi/hij
/abc/bcd/cdd
EOF

/abc/bcd/c, как и ожидалось. ✔️

Чтобы ограничить его компонентами пути:

sed -E '$!{N;s|^(.*/).*\n\1.*$|\1\n\1|;D;};s|/$||' <<'EOF'
/abc/bcd/cdf
/abc/bcd/cdf/foo
/abc/bcd/chi/hij
/abc/bcd/cdd
EOF

/abc/bcd, как и ожидалось. ✔️

Обработка компонентов пути с новой строкой

В целях тестирования мы будем использовать этот массив спецификаций пути:

a=(
  $'/a\n/b/\nc  d\n/\n\ne/f'
  $'/a\n/b/\nc  d\n/\ne/f'
  $'/a\n/b/\nc  d\n/\ne\n/f'
  $'/a\n/b/\nc  d\n/\nef'
)

Посмотрев, мы можем увидеть, что самый длинный общий ведущий компонент пути равен:

$'/a\n/b/\nc  d\n'

Это можно вычислить и зафиксировать в переменной с помощью следующего:

longest_common_leading_path_component=$(
  printf '%s\0' "${a[@]}" \
    | sed -zE '$!{N;s|^(.*/).*\x00\1.*$|\1\x00\1|;D;};s|/$||' \
    | tr \\0 x # replace trailing NUL with a dummy character ②
)
# Remove the dummy character
longest_common_leading_path_component=${longest_common_leading_path_component%x} 
# Inspect result
echo "${longest_common_leading_path_component@Q}" # ③

Результат:

$'/a\n/b/\nc  d\n'

как и ожидалось. ✔️


Продолжая наш тестовый пример, мы теперь покажем, как разложить спецификации пути на самые длинные общие ведущие компоненты пути + оставшиеся компоненты пути со следующими:

for e in "${a[@]}"; do
  remainder=${e#"$longest_common_leading_path_component/"}
  printf '%-26s -> %s + %s\n' \
    "${e@Q}" \
    "${longest_common_leading_path_component@Q}" \
    "${remainder@Q}"
done

Результат:

$'/a\n/b/\nc  d\n/\n\ne/f' -> $'/a\n/b/\nc  d\n' + $'\n\ne/f'
$'/a\n/b/\nc  d\n/\ne/f'   -> $'/a\n/b/\nc  d\n' + $'\ne/f'
$'/a\n/b/\nc  d\n/\ne\n/f' -> $'/a\n/b/\nc  d\n' + $'\ne\n/f'
$'/a\n/b/\nc  d\n/\nef'    -> $'/a\n/b/\nc  d\n' + $'\nef'

① Я всегда добавляю параметр -Eв sed и grep, чтобы переключить их на синтаксис ERE для лучшей согласованности с другими инструментами/языками, которые я использую, например, awk, bash, perl, javascript и Джава.

② Чтобы сохранить любые завершающие символы новой строки в этой подстановке команд, мы использовали обычную технику добавления фиктивного символа, который впоследствии обрезается. Мы объединили удаление завершающего NUL с добавлением фиктивного символа (, который мы выбралиx)за один шаг, используя tr \\0 x.

③ Расширение ${parameter@Q}приводит к «строке, которая является значением параметра в кавычках в формате, который можно повторно использовать в качестве входных данных». – Справочное руководство по bash . Требуется bash 4.4+(обсуждение). В противном случае вы можете проверить результат, используя один из следующих:

printf '%q' "$longest_common_leading_path_component"
printf '%s' "$longest_common_leading_path_component" | od -An -tc
od -An -tc < <(printf %s "$longest_common_leading_path_component")
od -An -tc <<<$longest_common_leading_path_component # ④

④ Имейте в виду, что здесь -строки добавляют новую строку(обсуждение).

0
17.03.2021, 23:37

Теги

Похожие вопросы