Попытка заставить работать общий многострочный шаблон sed и скрипт подстановки
Существует общий подход к решению проблемы, представленной на этом постере , который представлен на linuxtopia здесь , в разделе 4.23.3 . Похоже, он предлагает метод обработки любого сложного шаблона контента для сопоставления цели, а затем замены его, опять же, любым другим сложным шаблоном контента. Этот метод называется методом « скользящего окна ».
Я считаю, что приведенный ниже сценарий точно воссоздает описанный сценарий и пытается включить сценарий sed, чтобы продемонстрировать работоспособность этого подхода.
#!/bin/bash
DBG=1
###
### Code segment to be replaced
###
file1="File1.cpp"
rm -f "${file1}"
cat >"${file1}" <<"EnDoFiNpUt"
void Component::initialize()
{
my_component = new ComponentClass();
}
EnDoFiNpUt
test ${DBG} -eq 1 && echo "fence 1"
###
### Code segment to be used as replacement
###
file2="File2.cpp"
rm -f "${file2}"
cat >"${file2}" <<"EnDoFiNpUt"
void Component::initialize()
{
if (doInit)
{
my_component = new ComponentClass();
}
else
{
my_component.ptr = null;
}
}
EnDoFiNpUt
test ${DBG} -eq 1 && echo "fence 2"
###
### Create demo input file
###
testfile="Test_INPUT.cpp"
rm -f "${testfile}"
{
echo "
other code1()
{
doing other things
doing more things
doing extra things
}
"
cat "${file1}"
echo "
other code2()
{
creating other things
creating more things
creating extra things
}
"
} >>"${testfile}"
test ${DBG} -eq 1 && echo "fence 3"
###
### Create editing specification file
###
{
cat "${file1}"
echo "###REPLACE_BY###"
cat "${file2}"
} >findrep.txt
test ${DBG} -eq 1 && echo "fence 4"
###
### sed script to create editing instructions to apply aove editing specification file
###
cat >"blockrep.sed" <<"EnDoFiNpUt"
#SOURCE: https://www.linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/sedfaq4_013.html
#
# filename: blockrep.sed
# author: Paolo Bonzini
# Requires:
# (1) blocks to find and replace, e.g., findrep.txt
# (2) an input file to be changed, input.file
#
# blockrep.sed creates a second sed script, custom.sed,
# to find the lines above the row of 4 hyphens, globally
# replacing them with the lower block of text. GNU sed
# is recommended but not required for this script.
#
# Loop on the first part, accumulating the `from' text
# into the hold space.
:a
/^###REPLACE_BY###$/! {
# Escape slashes, backslashes, the final newline and
# regular expression metacharacters.
s,[/\[.*],\\&,g
s/$/\\/
H
#
# Append N cmds needed to maintain the sliding window.
x
1 s,^.,s/,
1! s/^/N\
/
x
n
ba
}
#
# Change the final backslash to a slash to separate the
# two sides of the s command.
x
s,\\$,/,
x
#
# Until EOF, gather the substitution into hold space.
:b
n
s,[/\],\\&,g
$! s/$/\\/
H
$! bb
#
# Start the RHS of the s command without a leading
# newline, add the P/D pair for the sliding window, and
# print the script.
g
s,/\n,/,
s,$,/\
P\
D,p
#---end of script---
EnDoFiNpUt
test ${DBG} -eq 1 && echo "fence 5"
sed --debug -nf blockrep.sed findrep.txt >custom.sed
test ${DBG} -eq 1 && echo "fence 6"
if [ -s custom.sed ]
then
more custom.sed
echo -e "\t Hit return to continue ..." >&2
read k <&2
else
echo -e "\t Failed to create 'custom.sed'. Unable to proceed!\n" >&2
exit 1
fi
testout="Test_OUTPUT.cpp"
sed -f custom.sed "${testfile}" >"${testout}"
test ${DBG} -eq 1 && echo "fence 7"
if [ -s "${testout}" ]
then
more "${testout}"
else
echo -e "\t Failed to create '${testout}'.\n" >&2
exit 1
fi
К сожалению, то, что они представили, похоже, не работает. Хотелось бы, чтобы было что-то вроде bash «set -x» для расширения команды / отчета о выполнении sed в stderr, но я не нашел ничего подобного.
Журнал выполнения для вышеуказанного выглядит следующим образом:
fence 1
fence 2
fence 3
fence 4
fence 5
sed: file blockrep.sed line 19: unterminated `s' command
fence 6
Failed to create 'custom.sed'. Unable to proceed!
Может быть, какой-нибудь эксперт сможет исправить логическую ошибку в импортированном скрипте blockrep.sed ... потому что я не могу понять, как это исправить, даже со всеми предоставленными комментариями.
Я открыто подтверждаю тот факт, что я очень упрощен/ограничен как в своих знаниях, так и в использовании sed. Я не мог понять, как этот скрипт « blockrep.sed » пытается делать то, что он утверждает, только то, что он указывает все содержимое findrep.txt перед определенной строкой разделителя « ###REPLACE_BY### » быть заменены на все ниже того же разделителя.
На мой взгляд, подход, указанный в руководстве по linuxtopia, будет иметь широкое применение и будет полезен для многих , включая ОП и меня.
1 ответ
Я прибегнул к дискретизации частей сценария blockrep.sed , чтобы посмотреть, смогу ли я определить источник сбоя. Хотя на первый взгляд это не имело никакого логического значения, это создало функциональную и правильно сформированную структуру... которая создала пригодный для использования custom.sed , но только после того, как я удалил параметр --debug для выполнения blockrep.sed. Это необходимо, потому что информация отладки не отправляется на стандартный вывод, а встроена в стандартный вывод !!!Я не знаю достаточно, чтобы классифицировать это как ошибку.
ПРИМЕЧАНИЕ. Я также добавил параметры командной строки для указания имен файлов для ввода, вывода, old_pattern, new_pattern, divider и других.
Теперь модифицированная и рабочая версия скрипта выглядит следующим образом:
#!/bin/bash
reportFiles ()
{
ls -l "${fileSrchPat}" "${fileReplPat}" "findrep.txt" 2>&1
ls -l "blockrep.sed" "custom.sed" "custom.err" 2>&1
ls -l "${fileBefore}" 2>&1
ls -l "${fileOutput}" 2>&1
}
DBG=0
DBGs=0
dumA=1 ;
dumB=1 ;
fileSrchPat=""
fileReplPat=""
divider="----REPLACE_BY----"
doReview=0
fileBefore=""
fileOutput=""
while [ $# -gt 0 ]
do
case $1 in
--debug )
DBG=1 ;
shift ;;
--debug_sed )
DBGs=1 ;
shift ;;
--verbose )
set -x
shift ;;
--old_pattern )
dumA=0 ;
fileSrchPat="$2" ;
if [ ! -s "${fileSrchPat}" ]
then
echo -e "\n File '${fileSrchPat}' not found.\n Bye!\n"
exit 1
fi ;
shift ; shift ;;
--new_pattern )
dumA=0 ;
fileReplPat="$2" ;
if [ ! -s "${fileReplPat}" ]
then
echo -e "\n File '${fileReplPat}' not found.\n Bye!\n"
exit 1
fi ;
shift ; shift ;;
--pattern_sep )
divider="$2" ;
shift ; shift ;;
--input )
dumB=0 ;
fileBefore="$2" ;
if [ ! -s "${fileBefore}" ]
then
echo -e "\n File '${fileBefore}' not found.\n Bye!\n"
exit 1
fi ;
shift ; shift ;;
--output )
fileOutput="$2" ;
if [ ! -s "${fileOutput}" ]
then
echo -e "\n File '${fileOutput}' already exists. Overwrite ? [y|N] => \c"
read goAhead
if [ -z "${goAhead}" ] ; then goAhead="N" ; fi
case ${goAhead} in
y* | Y* ) rm -vf "${fileOutput}" ;;
* ) echo -e "\n\t Process abandonned.\n Bye!\n" ; exit 1 ;;
esac
exit 1
fi ;
shift ; shift ;;
--review ) doReview=1 ; shift ;;
* ) echo "\n invalid option used on command line. Only valid options: [ --old_pattern {textfile1} | --new_pattern {textfile2} ] \n Bye!\n" ; exit 1 ;;
esac
done
###
### Code segment to be replaced
###
if [ -z "${fileSrchPat}" ]
then
fileSrchPat="File1.cpp"
rm -f "${fileSrchPat}"
cat >"${fileSrchPat}" <<"EnDoFiNpUt"
void Component::initialize()
{
my_component = new ComponentClass();
}
EnDoFiNpUt
fi
test ${DBG} -eq 1 && echo -e "\n\t ======== fence 1"
###
### Code segment to be used as replacement
###
if [ -z "${fileReplPat}" ]
then
fileReplPat="File2.cpp"
rm -f "${fileReplPat}"
cat >"${fileReplPat}" <<"EnDoFiNpUt"
void Component::initialize()
{
if (doInit)
{
my_component = new ComponentClass();
}
else
{
my_component.ptr = null;
}
}
EnDoFiNpUt
fi
test ${DBG} -eq 1 && echo -e "\n\t ======== fence 2"
if [ -z "${fileBefore}" ]
then
###
### Create demo input file
###
fileBefore="Test_INPUT.cpp"
rm -f "${fileBefore}"
{
echo "
other code1()
{
doing other things
doing more things
doing extra things
}
"
cat "${fileSrchPat}"
echo "
other code2()
{
creating other things
creating more things
creating extra things
}
"
} >>"${fileBefore}"
fi
test ${DBG} -eq 1 && echo -e "\n\t ======== fence 3"
###
### Create editing specification file
###
{
cat "${fileSrchPat}"
echo "${divider}"
cat "${fileReplPat}"
} >findrep.txt
test ${DBG} -eq 1 && echo -e "\n\t ======== fence 4"
###
### sed script to create editing instructions to apply above editing specification file
###
test ${DBG} -eq 1 && rm -fv "blockrep.sed" || rm -f "blockrep.sed"
cat >"blockrep.sed" <<"EnDoFiNpUt"
#SOURCE: https://www.linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/sedfaq4_013.html
#
# filename: blockrep.sed
# author: Paolo Bonzini
#
# Modified by: Eric Marceau, Feb 2023
#
# Requires:
# (1) blocks to find and replace, e.g., findrep.txt
# (2) an input file to be changed, input.file
#
# blockrep.sed creates a second sed script, custom.sed,
# to find the lines above the row of 4 hyphens, globally
# replacing them with the lower block of text. GNU sed
# is recommended but not required for this script.
#
# Loop on the first part, accumulating the `from' text
# into the hold space.
#
##############################################################################
### Reworked Discretized version of coding
##############################################################################
#
##############################################################################
### Begin of capture - SEARCH pattern
##############################################################################
:markerA
EnDoFiNpUt
#echo "/^----REPLACE_BY----\$/! {" >> "blockrep.sed"
echo "/^${divider}\$/! {" >> "blockrep.sed"
cat >>"blockrep.sed" <<"EnDoFiNpUt"
#
# Escape slashes
s,[/],\\&,g
#
# Escape backslashes
s,[\],\\&,g
#
# Escape regular expression metacharacters
s,[[],\\&,g
s,[.],\\&,g
s,[*],\\&,g
#
# Escape the final newline
# add backslash to end of line (to avoid having sed
# think of as end of command input)
s,$,\\,
#
# APPEND - PATTERN space to HOLD space
H
#
# Sequence to APPEND "N" cmds needed to maintain the sliding window.
# \\ swap contents - HOLD and PATTERN space
x
#
# If first line, begin constructing sed command for pattern match and replace
1 s,^.,s/,
#
# If not first line, add line with "N"
# i.e. give instruction to "APPEND the next line of input into the pattern space"
1! s,^,N\
,
# // swap contents again - HOLD and PATTERN space
x
#
# COPY - next line of input into PATTERN space
n
#
# branch/jump to label markerA
b markerA
}
#
##############################################################################
### End of capture - SEARCH pattern
##############################################################################
#
#
##############################################################################
### Begin of capture - REPLACEMENT pattern
##############################################################################
#
# \\ swap contents - HOLD and PATTERN space
x
#
# Change the final backslash to a slash to separate the
# two sides of the s command.
s,\\$,/,
#
# // swap contents again - HOLD and PATTERN space
x
#
# Until EOF, gather the REPLACEMENT TEXT into the hold space.
:markerB
n
#
# Escape slashes
s,[/],\\&,g
#
# Escape backslashes
s,[\],\\&,g
#
# If not last line, add backslash to escape all instances of "$".
$! s,$,\\,
#
# APPEND - PATTERN space to HOLD space
H
#
# If not last line, branch/jump to markerB
$! b markerB
#
##############################################################################
### End of capture - SEARCH pattern
##############################################################################
#
#
# Start the Right-Hand Side (RHS) of the "s" command without a leading newline,
# add the P/D pair for the sliding window, and
# print the script.
#
# COPY - HOLD space to PATTERN space
g
s,/\n,/,
#
# (P) Print up to the first embedded newline of the current pattern space.
# then
# (D) If pattern space contains no newline, start a normal new cycle as if
# the d command was issued. Otherwise, delete text in the pattern space
# up to the first newline, and restart cycle with the resultant pattern space,
# without reading a new line of input.
# then
# (p) Print the current pattern space.
s,$,/\
P\
D,p
#---end of script---
EnDoFiNpUt
test ${DBG} -eq 1 && echo -e "\n\t ======== fence 5"
test ${DBG} -eq 1 && rm -fv custom.sed custom.err || rm -f custom.sed custom.err
if [ ${DBGs} -eq 1 ]
then
echo -e "\n\t NOTE: debug mode active for 'sed' command ..."
sed --debug -f blockrep.sed findrep.txt >custom.sed 2>custom.err
else
sed -nf blockrep.sed findrep.txt >custom.sed 2>custom.err
fi
test ${DBG} -eq 1 && echo -e "\n\t ======== fence 6"
if [ -s custom.err ]
then
if [ ${doReview} -eq 1 ]
then
cat custom.err
fi
fi
test ${DBG} -eq 1 && echo -e "\n\t ======== fence 7"
if [ -s custom.sed ]
then
if [ ${doReview} -eq 1 ]
then
more custom.sed
echo -e "\n\t Hit return to continue ..." >&2
read k <&2
fi
if [ ${DBGs} -eq 1 ]
then
more custom.sed
echo -e "\n ============= End of Review - 'custom.sed' containing execution debug reporting =============" >&2
echo -e "\n\t 'custom.sed' is not in usable form due to '--debug' messaging." >&2
echo -e "\t Abandoning before attempting final transformation.\n Bye!\n" >&2
exit 2
fi
else
echo -e "\t Failed to create 'custom.sed'. Unable to proceed!\n" >&2
exit 1
fi
test ${DBG} -eq 1 && echo -e "\n\t ======== fence 8"
if [ -z "${fileOutput}" ]
then
fileOutput="Test_OUTPUT.cpp"
fi
rm -f "${fileOutput}"
sed -f custom.sed "${fileBefore}" >"${fileOutput}"
test ${DBG} -eq 1 && echo -e "\n\t ======== fence 9"
if [ -s "${fileOutput}" ]
then
test ${DBG} -eq 1 && echo -e "\n\t ======== fence 10"
if [ ${doReview} -eq 1 ]
then
more "${fileOutput}"
fi
if [ ${DBG} -eq 1 ]
then
reportFiles
if [ ${dumA} -eq 1 ]
then
rm -fv "${fileSrchPat}" "${fileReplPat}" 2>&1
fi
if [ ${dumB} -eq 1 ]
then
rm -fv "${fileBefore}" 2>&1
fi
rm -fv "findrep.txt" "custom.sed" "custom.err" "blockrep.sed" 2>&1
else
if [ ${dumA} -eq 1 ]
then
rm -f "${fileSrchPat}" "${fileReplPat}" 2>&1
fi
if [ ${dumB} -eq 1 ]
then
rm -f "${fileBefore}" 2>&1
fi
rm -f "findrep.txt" "custom.sed" "custom.err" "blockrep.sed" 2>&1
fi | awk '{ printf("\t %s\n", $0 ) ; }' >&2
else
echo -e "\t Failed to create '${fileOutput}'.\n" >&2
reportFiles | awk '{ printf("\t %s\n", $0 ) ; }' >&2
exit 1
fi
exit
Использование следующей команды для выполнения (используя встроенный демо-кейс по умолчанию)
./script.sh --debug --review
Результирующий вывод сеанса
======== fence 1
======== fence 2
======== fence 3
======== fence 4
======== fence 5
======== fence 6
======== fence 7
N
N
N
s/void Component::initialize()\
{\
my_component = new ComponentClass();\
}/void Component::initialize()\
{\
if (doInit)\
{\
my_component = new ComponentClass();\
}\
else\
{\
my_component.ptr = null;\
}\
}/
P
D
Hit return to continue ...
======== fence 8
======== fence 9
======== fence 10
other code1()
{
doing other things
doing more things
doing extra things
}
void Component::initialize()
{
if (doInit)
{
my_component = new ComponentClass();
}
else
{
my_component.ptr = null;
}
}
other code2()
{
creating other things
creating more things
creating extra things
}
-rw-rw-r-- 1 ericthered ericthered 71 Feb 4 16:05 File1.cpp
-rw-rw-r-- 1 ericthered ericthered 130 Feb 4 16:05 File2.cpp
-rw-rw-r-- 1 ericthered ericthered 220 Feb 4 16:05 findrep.txt
-rw-rw-r-- 1 ericthered ericthered 3604 Feb 4 16:05 blockrep.sed
-rw-rw-r-- 1 ericthered ericthered 0 Feb 4 16:05 custom.err
-rw-rw-r-- 1 ericthered ericthered 229 Feb 4 16:05 custom.sed
-rw-rw-r-- 1 ericthered ericthered 240 Feb 4 16:05 Test_INPUT.cpp
-rw-rw-r-- 1 ericthered ericthered 299 Feb 4 16:06 Test_OUTPUT.cpp
removed 'File1.cpp'
removed 'File2.cpp'
removed 'Test_INPUT.cpp'
removed 'findrep.txt'
removed 'custom.sed'
removed 'custom.err'
removed 'blockrep.sed'
Что, как и было задумано изначально. Успех!