Sed не учитывает символ адреса $, когда я пытаюсь сопоставить только последнее вхождение из скрипта

Вам нужно экранировать (обратной косой чертой\)все замененные косые черты /и все обратные косые черты \отдельно, поэтому:

$ echo "/tmp/test/folder1/test.txt" | sed 's/\//\\\//g'
\/tmp\/test\/folder1\/test.txt

но это довольно нечитабельно.

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

echo "/tmp/test/folder1/test.txt" | sed 's;/;\\/;g'

Другие случаи:

  • Если кто-то хочет придерживаться косой черты в качестве разделителя и использовать двойные кавычки, тогда все экранированные обратные косые черты должны быть экранированы еще раз, чтобы сохранить их буквальные значения:

    echo "/tmp/test/folder1/test.txt" | sed "s/\//\\\\\//g"
    
  • если вообще не нужны кавычки, то нужна еще одна обратная косая черта:

    echo "/tmp/test/folder1/test.txt" | sed s/\\//\\\\\\//g
    
2
11.10.2019, 01:39
2 ответа

Анкер $:

Matches the end of a string without consuming any characters. If multiline mode is used, this will also match immediately before a newline character.

Это отличается от "последнего появления", так как ваши строки на самом деле заканчиваются на txt$, а не на bar$и не совпадают.

Один из способов сопоставить только последнее вхождение — использовать revдля реверсирования вашей строки и замены только первого вхождения (, конечно, ваши замены также должны быть реверсированы!)

function searchReplaceLastMatchOnly() {
  # spaces are set as defaults
  local target=$(rev <<<"${1:- }")
  local search=$(rev <<<"${2:- }")
  local replace=$(rev <<<"${3:- }")
  local esearch=$(printf '%s\n' "$search" | sed 's:[][\/.^$*]:\\&:g')
  local ereplace=$(printf '%s\n' "$replace" | sed 's:[\/&]:\\&:g;$!s/$/\\/')
  result="$(printf "%s" "$target" | sed "s/${esearch}/${ereplace}/" | rev)"
  printf "%s" "$result"
}

Обратите внимание, :оператор gбыл удален из команды sed, поэтому заменяется только первое вхождение . Кроме того, мы экранируем переменные , которые будут переданы в sed, чтобы предотвратить их нежелательную интерпретацию.

1
27.01.2020, 22:02

Чтобы заменить только последнее совпадение, замените

result="$(printf "%s" "$target" | sed "s/${search}$/${replace}/g")

с

result="$(printf "%s" "$target" | sed "s/\(.*\)${search}/\1${replace}/")"

$соответствует концу строки, а не последнему шаблону поиска, и вам не нужен модификатор g, так как существует (не более )одной замены.

\(.*\)является жадным и sedсопоставляет все до того, как найдет последний шаблон $search. И поскольку мы не хотим удалять эту часть, мы должны включить ее как \1в замену.

Примечания:

  • В сценарии отсутствует шебанг #!/bin/bashв первой строке.
  • -eеще не ()реализовано.
  • Существует множество переменных, которые используются только один раз. Их удаление сделает ваш код намного чище, например. функция searchReplaceLastMatchOnly()может быть сведена к

    function searchReplaceLastMatchOnly() {
      printf "%s" "${1:- }" | sed "s/\(.*\)${2:- }/\1${3:- }/"
    }
    
2
27.01.2020, 22:02

Теги

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