Скажем, у меня есть следующие файлы:
/etc/dir1/file.txt
/etc/dir2/file.txt
/etc/dir3/file.txt
... полностью до dir100
(100 каталогов), каждый каталог имеет file.txt
.
И у меня есть следующий текстовый файл в /root/list.txt
. В list.txt
, У меня есть 100 строк, каждая строка с различной строкой текста.
В каждом file.txt
, существует строка текста, word1
.
Как я использовал бы sed
(или что-то подобное) для замены слова word1
в каждом file.txt
, с одной строкой от list.txt
? Каждая строка в list.txt
, только, чтобы использоваться однажды.
Так, например, замена word1
в /etc/dir1/file.txt
с первой строкой в /root/list.txt
, и замена word1
/etc/dir2/file.txt
со второй строкой в /root/list.txt
и так далее, полностью до 100.
Я значительно ценю любую справку и помощь здесь как sed
не моя сильная сторона.
sed
в цикле, если строки list.txt
хорошего поведения.Как я использовал бы
sed
(или что-то подобное) для замены словаword1
в каждомfile.txt
, с одной строкой отlist.txt
? Каждая строка вlist.txt
, только, чтобы использоваться однажды.
Ubuntu имеет GNU sed, который помогает заменить только первое вхождение шаблона в файле, затем остановиться. Для использования отдельных замещающих строк для каждого входного файла можно использовать цикл. Код ниже просто достаточно сложен, что я предлагаю делать его сценарием и запустить скрипт. Существует три главных протеста:
word1
- то, которое я предполагаю, что можно измениться на что-то еще - не должно содержать /
, если Вы не используете другой разделитель в sed
команда. И при этом это не может содержать символы sed
обработки особенно, такие как метасимволы регулярного выражения (\
, *
, .
, и т.д), если это не то, что Вы предназначаете.list.txt
не должен содержать /
или наиболее специальные символы также.dir1
, dir2
..., находятся в /etc
и Ваш list.txt
находится в /root
, но я записал свой сценарий, чтобы предположить, что те каталоги - и тот файл - находятся в текущем каталоге вместо этого. Я сделал это, потому что файлы, хранившие в тех местоположениях, часто важны, и я предполагаю, что Вы захотите протестировать этот сценарий - и возможно сделать Ваши собственные модификации - перед использованием его для реального. Можно изменить сценарий для использования местоположений, которые Вы дали, или любые другие местоположения, в которых Вы нуждаетесь.У меня есть полужирный № 2 becuase, это - то, которое я ожидаю, мог бы вызвать Вас проблема, в зависимости от какой list.txt
мог бы содержать. Теперь, когда Вас предупредили, вот сценарий:
#!/bin/bash
mapfile -t <list.txt
for ((i=1; i<=${#MAPFILE[@]}; ++i))
do sed -i.bak "0,/word1/ s//${MAPFILE[i-1]}/" "dir$i/file.txt"
done
Это - все, что требуется. В случае, если Вам интересно, вот то, как это работает:
mapfile
Bash, встроенный, который читает строки в массив. Я использую его для чтения из list.txt
. Я не указывал имя массива так имя по умолчанию MAPFILE
используется.for
цикл, который полезен, когда каждый хочет циклично выполниться от или до значения, полученного расширением параметра, начиная с расширения фигурной скобки в Bash, не развернет вещи как {1..$var}
. Я использую его для цикличного выполнения от 1 до длины MAPFILE
массив.sed -i
заменяет исходный файл. Вы теряете старую версию, если Вы не обеспечиваете резервный суффикс. Можно удалить .bak
от команды, если Вы не хотите сохранять старую версию, но я рекомендую рассмотреть хранение ее. По крайней мере, тест с ним прежде, чем удалить его.0,/word1/ s//REPLACEMENT/
работает только в диапазоне с начала файла к первому соответствию с word1
(0,/word1/
), заменяя текст, соответствующий тому же самому шаблону REPLACEMENT
(s//*REPLACEMENT*/
). Это должно сказать, что заменяет только первое соответствие во входе. Остальная часть входа неизменна независимо от того, соответствовало ли это шаблону.0
, но Ваши файлы называют, начиная с 1
, который является, где я решил переменную цикла $i
должен запуститься. К счастью, это легко иметь дело с тем, потому что массивы Bash принимают арифметические выражения как индексы. ${MAPFILE[i-1]}
расширяется до элемента i - 1 (то есть, ith элемента) массива строк от list.txt
.sed
.Если Вы терпеть не можете протест № 2 - то есть, строки в list.txt
могло быть примерно что-либо - затем я не знаю о хорошем способе сделать это с sed
. Но существует много альтернатив. Bash может на самом деле сделать это само без любых внешних команд, и я покажу почти решение чистого Bash. (Возможно, больше ответов будет отправлено для показа использования методов awk
или другие утилиты.)
В sed
, шаблон является регулярным экспрессом. Но этот метод рассматривает шаблон как шарик. См. также эту запись FOLDOC и man 7 glob
. Таким образом специальные символы включая *
, ?
, [
, и ]
- и некоторые другие как \
- имейте особые значения в word1
(просто не обычно те же значения как в sed
). Если word1
буквально word1
или любой другой текст без globbing символов, без проблем. Иначе необходимо изменить его соответственно. Этот метод удаляет протест № 2, но не протест № 1 - ни № 3, но можно иметь дело с этим сами легко.
#!/bin/bash
pattern='word1' # text to search for
suffix='.bak' # suffix to append for backup files
mapfile -t <list.txt
for ((i=1; i<=${#MAPFILE[@]}; ++i)); do
name="dir$i/file.txt"
mv "$name" "$name$suffix" || exit # quit with an error if we can't rename
{
while read -r; do # output up to and including the replacement
case "$REPLY" in
*"$pattern"*)
printf '%s\n' "${REPLY/$pattern/${MAPFILE[i-1]}}"
break ;;
*)
printf '%s\n' "$REPLY" ;;
esac
done
while read -r; do # output the rest
printf '%s\n' "$REPLY"
done
} <"$name$suffix" >"$name"
done
В случае, если Вы (все еще) заинтересованы, вот то, как это работает:
list.txt
в массив. Я мог также считать каждого file.txt
в массив, но кто знает эти файлы могли быть огромными, таким образом, я читаю их одна строка за один раз с read -r
вместо этого..bak
суффикс с mv
. mv
единственная внешняя утилита этот сценарий использование. Я не потрудился передавать --
прежде чем путь, потому что в этом случае нет никакого пути пути, может запуститься с -
. Если операция пересылки перестала работать, mv
произведет ошибку и || exit
завершает сценарий, предотвращая случайную потерю данных. Единственные данные, которые должно быть возможно потерять путем запущения этого скрипта, являются данными в существовании ранее .bak
файлы.{
}
. У целой группы есть оба своих ввода и вывода, перенаправленные для использования .bak
файл, как введено и файл, названный тем же как оригинал, как произведено (<"$name$suffix" >"$name"
).read -r
самостоятельно изменяет их, должен удалить символы новой строки в концах (который printf
возвратится с \n
позже). В Bash, read -r
без имени переменной читает строку в $REPLY
и не разделяет ведущий и запаздывающий пробел; это эквивалентно IFS= read -r REPLY
.*
), сопровождаемый word1
("$pattern
"), сопровождаемый снова любым или никакими символами (*
). Когда это находит такую строку, это печатает его, но заменяет часть, которая соответствует "$pattern"
с ith строкой от list.txt
(${MAPFILE[i-1]}
) и затем повреждает цикл. Все строки перед той просто печатаются дословно.При помощи двух группировавшихся циклов я достиг той же основной логики как sed
путь детализировал выше - текст до, включая, но не вне первого соответствия обрабатывается сначала, таким образом, соответствием заменяют, затем последующий текст не ищется вообще, просто копируется. Однако, в отличие от этого, в этом sed
метод, специальные символы, в какой ${MAPFILE[i-1]}
расширяется до, не рассматриваются как часть команды.
Например, заметьте, что замещающая строка, которая пыталась доставить неприятности путем закрытия внутреннего расширения параметра и введения дополнительных замен, не успешно выполнится:
$ s=foobarbaz t=bar u='}$s$s$s'; echo "${s/$t/${u}}"
foo}$s$s$sbaz
Давайте создадим тестовую среду, помещенную в пользователя $HOME
каталог.
Сначала выполните следующую строку как единственную команду:
path="${HOME}/etc/dir"; for i in {1..100}; do mkdir -p "$path$i" ; echo -e "$path$i/file.txt:\nline1 some text here\nline2 word1 some text here word1\nline3 word1 some text here" > "$path$i/file.txt"; done
Это создаст сотню каталогов - ~/etc/dir{1..100}
. В каждом каталоге будет создан также файл, названный file.txt
, это содержит строку word1
несколько раз:
$ cat ~/etc/dir{1..100}/file.txt
/home/<user>/etc/dir1/file.txt:
line1 some text here
line2 word1 some text here word1
line3 word1 some text here
/home/<user>/etc/dir2 file.txt:
...
Затем выполните эту строку:
path="${HOME}/root" && mkdir "$path"; for i in {1..100}; do echo '*{string line ['"$i"']}*' >> "$path/list.txt"; done
Это создаст каталог, названный ~/root
. В каталоге будет создан также файл, названный list.txt
, это содержит сотню строк:
$ cat ~/root/list.txt
*{string line [1]}*
*{string line [2]}*
...
Давайте решим задачу. Согласно обстоятельствам, создайте в вышеупомянутый шаг, потому что строка word1
несколько раз происходит, у нас есть несколько случаев. Решения в качестве примера:
Заменять только первое вхождение word1
в каждом file.txt
, выполните эту строку:
i=""; while read line; do i=$((i+1)); sed "0,/word1/ s|\word1|${line}|1" "$HOME/etc/dir$i/file.txt"; done < "$HOME/root/list.txt"
Вывод должен быть:
/home/<user>/etc/dir1/file.txt:
line1 some text here
line2 *{string line [1]}* some text here word1
line3 word1 some text here
/home/<user>/etc/dir2 file.txt:
...
Заменять только первое вхождение word1
на каждой строке в каждом file.txt
, выполните эту строку:
i=""; while read line; do i=$((i+1)); sed "s|\word1|${line}|1" "$HOME/etc/dir$i/file.txt"; done < "$HOME/root/list.txt"
Вывод должен быть:
/home/<user>/etc/dir1/file.txt:
line1 some text here
line2 *{string line [1]}* some text here word1
line3 *{string line [1]}* some text here
/home/<user>/etc/dir2 file.txt:
...
Заменять все случаи word1
в каждом file.txt
, выполните эту строку:
i=""; while read line; do i=$((i+1)); sed "s|\word1|${line}|g" "$HOME/etc/dir$i/file.txt"; done < "$HOME/root/list.txt"
Вывод должен быть:
/home/<user>/etc/dir1/file.txt:
line1 some text here
line2 *{string line [1]}* some text here *{string line [1]}*
line3 *{string line [1]}* some text here
/home/<user>/etc/dir2 file.txt:
...
Примечания. В вышеупомянутые примеры:
Изменение sed
с sed -i
для фактической замены строк или использования sed -i.bak
сделать замены и оставить также файл резервной копии.
Удалить $HOME
согласно обстоятельствам, описанным в вопрос.