Перебирая файлы в директории;Вытащить имена файлов, чтобы заменить строки в существующих файлах - PullRequest
0 голосов
/ 29 марта 2019

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

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

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

#!/bin/bash

for file in /home/user/dir/*; do

  str="somestring"
  filename=$(basename $file)
  fn="$(echo "${filename%.*}")"

  find ./ -type f -exec sed -i '' -e "s/${str}/${fn}/g" {} \;

done

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

123456789.md и находится по адресу /home/user/dir/123456789.md с несколькими другими файлами .md с другими случайными числовыми именами.

Структура файлов .md похожа на:

---
layout: default
date: 2010-03-28
original: /orig/somestring.jpg
thumbnail: /thumb/somestring_thumb.jpg
permalink: /images/somestring/
---

и моя цель - сделать так, чтобы каждый файл выглядел так, основываясь на имени файла самого файла .md:

---
layout: default
date: 2010-03-28
original: /orig/123456789.jpg
thumbnail: /thumb/123456789_thumb.jpg
permalink: /images/123456789/
---

Есть какие-нибудь мысли о том, как лучше всего отредактировать вызов sed или как написать это? Иногда в моем тестировании sed возвращал sed: RE error: illegal byte sequence, но в любом случае проходил переименование строки, даже если это была неправильная строка.

Ответы [ 2 ]

1 голос
/ 29 марта 2019

Рассмотрите возможность использования следующего решения, которое является довольно надежным. Он гарантирует, что любой символ в заданной строке поиска и / или имени файла Markdown, который может быть интерпретирован как метасимвол basic регулярное выражение (BRE), будет рассматриваться как литерал в замене sed.

Решение:

#!/usr/bin/env bash

target_dir=/path/to/dir
search='somestring'

search_escaped=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<<"$search")

while read -rd ''; do
  base=$(basename -- "$REPLY")
  replace_escaped=$(sed 's/[&/\]/\\&/g' <<<"${base%.*}")
  sed -i '' -e 's/'"$search_escaped"'/'"$replace_escaped/g"'' "$REPLY"
done < <(find $target_dir -depth 1 -type f -name '*.md' -print0)

Пояснение:

  • Значение переменной target_dir должно быть определено как путь к каталогу, в котором вы хотите выполнить поиск. Например, /home/user/dir, как указано в вашем вопросе.

  • Значение переменной search должно быть изменено на строку, которую вы хотите найти в файлах уценки (.md), и оно должно быть заключено в одинарные кавычки ('...').

  • Строка с надписью;

    search_escaped=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<<"$search")
    

    экранирует потенциальные метасимволы BRE, которые могут существовать в вашей строке search, и присваивает результат новой переменной с именем search_escaped.

    Мы делаем это потому, что в конечном итоге заданная вами строка поиска будет использоваться как строка search с командой sed s , т.е. s/regexp/replacement/flags , По сути, каждый символ заданной строки search помещается в собственный набор символов [...], чтобы рассматривать его как литерал, за исключением символа (^) каретки, поскольку они экранируются как \^. Для получения дополнительной информации см. этот ответ .

    Это означает, что мы можем предоставить строку search, такую ​​как s$o.m *e[s\t^ring, то есть одну со многими метасимволами, и они будут рассматриваться как литералы и не позволят нашей программе ошибиться.

  • Используя утилиту find, мы определяем следующую команду для получения пути ко всем файлам .md в данном target_dir:

    find $target_dir -depth 1 -type f -name '*.md' -print0
    
    • Часть -depth 1 обеспечивает поиск файлов только на верхнем уровне. Однако, если вы хотите рекурсивно спускаться по указанному дереву каталогов, вы можете удалить его - удалив его, вы также включите любые файлы .md в подкаталоги данного каталога на много уровней.

    • Часть -name '*.md' гарантирует, что мы включаем только файлы Markdown (.md) и исключаем любые другие файлы, которые могут существовать в данном target_dir.

    • Часть find, заключенная в <( ... ), которая называется подстановка процесса , а предшествующая < перенаправляет пути, найденные find до stdin.

  • Цикл while читает s результаты команды find, т.е. пути к каждому найденному файлу .md.

    В теле цикла while мы выполняем следующие задачи:

    • Мы получаем basename из каждого пути (Примечание: $REPLY - встроенная переменная, связанная с while - в этом сценарии она содержит ссылку на имя пути во время каждого поворота цикл):

      base=$(basename -- "$REPLY")
      
    • Строка, которая гласит:

      replace_escaped=$(sed 's/[&/\]/\\&/g' <<<"${base%.*}")
      

      экранирует то, что sed может восприниматься как символ-заполнитель, такой как \1 в имени файла. Например; если файл с именем somefile\1\2\3.md потерпит неудачу, если мы заменим на него строку search - однако это защитит от этого. Снова, обратитесь к этому ответу для получения дополнительной информации.

      Часть ${base%.*} использует расширение параметра , чтобы опустить часть расширения файла (т.е. .md) из значения переменной base (т.е. из имени файла / базового имени).

    • Наконец, мы заменяем все экземпляры строки search (т.е. значение переменной $search_escaped), которые могут существовать в файле Markdown, значением переменной replace_escaped ( т.е. имя файла без расширения файла).

      sed -i '' -e 's/'"$search_escaped"'/'"$replace_escaped/g"'' "$REPLY"
      

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

0 голосов
/ 29 марта 2019

Если я правильно понимаю, будет работать следующее:

#!/bin/bash

for file in /home/user/dir/*; do

    str="somestring"
    filename=$(basename "$file")
    fn=${filename%.*}

    LANG=C sed -i '' -e "s/${str}/${fn}/g" "$file"

done

Проблема в том, что вы выполняете find & sed в цикле for, который переопределяет строки в несвязанных файлах.
LANG=C до sed будет обычным решением проблемы sed: RE error: illegal byte sequence.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...