У меня есть файл foo.txt
test
qwe
asd
xca
asdfarrf
sxcad
asdfa
sdca
dac
dacqa
ea
sdcv
asgfa
sdcv
ewq
qwe
a
df
fa
vas
fg
fasdf
eqw
qwe
aefawasd
adfae
asdfwe
asdf
era
fbn
tsgnjd
nuydid
hyhnydf
gby
asfga
dsg
eqw
qwe
rtargt
raga
adfgasgaa
asgarhsdtj
shyjuysy
sdgh
jstht
ewq
sdtjstsa
sdghysdmks
aadfbgns,
asfhytewat
bafg
q4t
qwe
asfdg5ab
fgshtsadtyh
wafbvg
nasfga
ghafg
ewq
qwe
afghta
asg56ang
adfg643
5aasdfgr5
asdfg
fdagh5t
ewq
Я хочу распечатать все строки между qwe
и ewq
в отдельном файле.
Это то, что у меня есть до сих пор:
#!/bin/bash
filename="foo.txt"
#While loop to read line by line
while read -r line
do
readLine=$line
#If the line starts with ST then echo the line
if [[ $readLine = qwe* ]] ; then
echo "$readLine"
read line
readLine=$line
if [[ $readLine = ewq* ]] ; then
echo "$readLine"
fi
fi
done < "$filename"
Вам необходимо внести некоторые изменения в свой скрипт (без определенного порядка):
IFS =
перед read
, чтобы избежать удаления передних и конечных пробелов.$ line
нигде не изменяется, нет необходимости в переменной readLine
.С этими изменениями скрипт становится:
#!/bin/bash
filename="foo.txt"
#While loop to read line by line
while IFS= read -r line; do
#If the line starts with ST then set var to yes.
if [[ $line == qwe* ]] ; then
printline="yes"
# Just t make each line start very clear, remove in use.
echo "----------------------->>"
fi
# If variable is yes, print the line.
if [[ $printline == "yes" ]] ; then
echo "$line"
fi
#If the line starts with ST then set var to no.
if [[ $line == ewq* ]] ; then
printline="no"
# Just to make each line end very clear, remove in use.
echo "----------------------------<<"
fi
done < "$filename"
Который может быть сжат таким образом:
#!/bin/bash
filename="foo.txt"
while IFS= read -r line; do
[[ $line == qwe* ]] && printline="yes"
[[ $printline == "yes" ]] && echo "$line"
[[ $line == ewq* ]] && printline="no"
done < "$filename"
Это напечатает начальную и конечную строки (включительно). Если нет необходимости их печатать, поменяйте начальный и конечный тесты:
#!/bin/bash
filename="foo.txt"
while IFS= read -r line; do
[[ $line == ewq* ]] && printline="no"
[[ $printline == "yes" ]] && echo "$line"
[[ $line == qwe* ]] && printline="yes"
done < "$filename"
Однако было бы гораздо лучше (если у вас есть версия bash 4.0 или выше) использовать readarray
и цикл с элементами массива:
#!/bin/dash
filename="infile"
readarray -t lines < "$filename"
for line in "${lines[@]}"; do
[[ $line == ewq* ]] && printline="no"
[[ $printline == "yes" ]] && echo "$line"
[[ $line == qwe* ]] && printline="yes"
done
Это позволит избежать большинства проблем использования read
.
Конечно, вы можете использовать рекомендованную (в комментариях; Спасибо, @costas) строку sed
, чтобы получить только линии для обработки:
#!/bin/bash
filename="foo.txt"
readarray -t lines <<< "$(sed -n '/^qwe.*/,/^ewq.*/p' "$filename")"
for line in "${lines[@]}"; do
: # Do all your additional processing here, with a clean input.
done
Как указал @Costas, правильный инструмент для этой работы - sed
:
sed '/qwe/,/ewq/ w other.file' foo.txt
На линиях для печати может потребоваться другая обработка. Хорошо; просто сделай так:
sed -e '/qwe/,/ewq/{w other.file' -e 'other processing;}' foo.txt
(Конечно, «другая обработка» не является настоящей командой «sed».) Выше приведен шаблон, который нужно использовать, если вам нужно выполнить обработку после печати строки. Если вы хотите выполнить какую-либо другую обработку, а затем распечатать измененную версию строки (что кажется более вероятным), вы бы использовали:
sed -e '/qwe/,/ewq/{processing;w other.file' -e '}' foo.txt
(Обратите внимание, что необходимо поместить }
в свой аргумент, в противном случае он будет интерпретироваться как часть имени other.file
.)
Вы (ОП) не указали, какую «другую обработку» вы должны выполнять в строках, или я мог бы быть более конкретным. Но какой бы ни была эта обработка, вы определенно можете сделать это в sed
- или, если это станет слишком громоздким, вы можете сделать это в awk
с очень небольшим изменением вышеуказанного кода:
awk '/qwe/,/ewq/ { print > "other.file" }' foo.txt
Затем у вас есть все возможности языка программирования awk
для обработки строк перед выполнением этого оператора print
. И, конечно, awk
(и sed
) разработаны для обработки текста, в отличие от bash
.
qwe(){ printf %s\\n "$1"; }
ewq(){ :; }
IFS= ### prep the loop, only IFS= once
while read -r in
do case $in in
(qwe|ewq)
set "$in"
;;
("$processing"?)
"$process"
esac
"$1" "$in"
done
Это один действительно медленный способ сделать это. С GNU grep
и обычным infile
:
IFS=
while grep -xm1 qwe
do while read -r in &&
[ ewq != "$in" ]
do printf %s\\n "$in"
: some processing
done
done <infile
. хотя бы оптимизировал половину неэффективных чтений..
sed -ne '/^qwe$/,/^ewq$/H;$!{/^qwe$/!d;}' \
-e "x;s/'"'/&\\&&/g;s/\n/'"' '/g" \
-e "s/\(.*\) .e.*/p '\1/p" <input |
sh -c 'p(){ printf %s\\n "$@"
for l do : process "$l"
done
}; . /dev/fd/0'
И это позволило бы избежать неэффективности «читать» в целом для большинства «sh», хотя он должен печатать выходные данные дважды - один раз в кавычки «sh» и один раз без кавычек в stdout. Это работает по-другому, потому что .Команда
имеет тенденцию читать ввод блоком, а не байтом для большинства реализаций. Тем не менее, он полностью использует ewq - qwe и будет работать для потокового ввода, такого как FIFO
qwe
asd
xca
asdfarrf
sxcad
asdfa
sdca
dac
dacqa
ea
sdcv
asgfa
sdcv
qwe
a
df
fa
vas
fg
fasdf
qwe
aefawasd
adfae
asdfwe
asdf
era
fbn
tsgnjd
nuydid
hyhnydf
gby
asfga
dsg
qwe
rtargt
raga
adfgasgaa
asgarhsdtj
shyjuysy
sdgh
jstht
qwe
asfdg5ab
fgshtsadtyh
wafbvg
nasfga
ghafg
qwe
afghta
asg56ang
adfg643
5aasdfgr5
asdfg
fdagh5t
sed '/./=' input2.file | sed -n '/./N;s/\n/ /; /qwe/,/ewq/p'