GNU head
и tail
с тех пор coreutils версия 8.25 имеют a -z
опция для этого.
С более старыми версиями или для систем не-GNU, можно попытаться подкачать \0
и \n
:
find ... -print0 |
tr '\0\n' '\n\0' |
head |
tr '\0\n' '\n\0'
Обратите внимание что некоторые head
реализации не могут справиться с символами NUL (и они не обязаны POSIX), но где находят поддержки -print0
, head
и текстовые утилиты обычно поддерживают символы NUL.
Можно также использовать функцию для обертывания любой команды между двумя tr
s:
nul_terminated() {
tr '\0\n' '\n\0' | "$@" | tr '\0\n' '\n\0'
}
find ... -print0 | nul_terminated tail -n 12 | xargs -r0 ...
Следует иметь в виду это под nul_terminated
, a \0
означает символ новой строки. Так, например, для замены \n
с _
:
find . -depth -name $'*\n*' -print0 | nul_terminated sed '
p;h;s,.*/,,;s/\x0/_/g;H;g;s,[^/]*\n,,' | xargs -r0n2 mv
(\x0
будучи также расширением GNU).
Если необходимо выполнить больше чем одну команду фильтрации, можно сделать:
find ... -print0 |
nul_terminated cmd1 |
nul_terminated cmd2 | xargs -r0 ...
Но это означает выполнять некоторых избыточных tr
команды. С другой стороны, можно работать:
find ... -print0 | nul_terminated eval 'cmd1 | cmd2' | xargs -r0 ...
Вам действительно следует использовать для этого парсер, но, чтобы вы знали, sed -n '/
дает вам все элементы, потому что вы p
их все списываете. Эта команда работает, обращаясь ко всем строкам между строкой, содержащей
, и следующей строкой ввода, содержащей
. Так как это делает почти все ваши строки, простая их промывка p
не покажет большой разницы. Что-то вроде следующего может быть немного ближе к метке:
sed -n '\|<tag>|{:n
\|</tag>|!{N;bn}
y|\n| |;p
}'
Он обращается к
строкам и проверяет их на наличие
. Если они не содержат закрывающую строку, он втягивает другую строку - и это повторяется до тех пор, пока пространство шаблонов не будет содержать
.
Затем я просто перевожу все \ n
символы ewline в пространстве шаблонов в пробелы.
Вот оно снова:
sed -n '\|<tag>|{:n;\|</tag>|!{N;bn};y|\n| |;p}' <<\DATA
<?xml version="1.0" encoding="UTF-8"?>
<root>
<tag>
<t1>john</t1>
<t2>john</t2>
<t3>john</t3>
</tag>
<tag>
<t1>john</t1>
<t2>john</t2>
<t3>john</t3>
</tag>
<tag>
<t1>john</t1>
<t2>john</t2>
<t3>john</t3>
</tag>
<tag>
<t1>john</t1>
<t2>john</t2>
<t3>john</t3>
</tag>
</root>
DATA
<tag> <t1>john</t1> <t2>john</t2> <t3>john</t3> </tag>
<tag> <t1>john</t1> <t2>john</t2> <t3>john</t3> </tag>
<tag> <t1>john</t1> <t2>john</t2> <t3>john</t3> </tag>
<tag> <t1>john</t1> <t2>john</t2> <t3>john</t3> </tag>
Теперь вы можете сделать:
sed -n '\|<tag>|{:n
\|</tag>|!{N;bn}
y|\n| |;p
}' ./file |
sed 's|> |>\n|g;2q'
... что меня поймает:
<tag>
<t1>john</t1>
<t2>john</t2>
<t3>john</t3>
</tag>
<tag>
<t1>john</t1>
<t2>john</t2>
<t3>john</t3>
</tag>
Труба через головку - N . После первых N вхождений head завершает работу и, таким образом, sed останавливается.
Насколько я знаю, совпадения sed
всегда жадные, т.е. /
будет соответствовать от первого экземпляра
до последнего экземпляра <\ tag>
- включая любые другие объекты XML между ними.
Если ваша версия awk
поддерживает многосимвольные разделители записей, вы можете сделать что-то вроде
awk -v n=2 'BEGIN{RS="</tag>\n";ORS=RS} NR<=n'
, но на самом деле более надежным решением будет использование выделенного синтаксического анализатора XML - например, очень минимальная реализация с использованием python minidom
#!/usr/bin/python
from xml.dom import minidom
xmldoc = minidom.parse('file.xml')
taglist = xmldoc.getElementsByTagName('tag')
for i in range(2) :
print taglist[i].toxml()
Я думаю, это то, что вам нужно,
sed -n '/<tag>/,/<\/tag>/p' file.xml | head -10
Попробуйте выполнить команду ниже, чтобы получить первые две строки, которые начинаются с
,
$ sed -n '/^<tag>/p' file.xml | head -2
<tag><t1>john</t1></tag>
<tag><t1>john</t1></tag>
Что ж, в конце концов я отвечу себе на свой вопрос.
Решение, которое я нашел, работает в 2 (может быть, 3) шагах:
1 - Получение всех требуемых элементов с помощью:
sed -n '/<tag>/,/<\/tag>/p' file.xml > selectedItems.xml
2 - Получение N-й позиции последнего элемента с помощью
POS = grep -n '</tag>' ./selectedItems.xml | head -n [POS] | tail -n 1
3 - Получение первых N требуемых элементов:
sed -n 1,[POS]p selectedItems.xml > selectedItems.xml
Конечно, можно выполнить все шаги без разделения, но это не будет так ясно.
PS Чтобы убедиться, что позиция соответствует реальной N-й позиции в дереве (когда XML-файл формируется полностью в строке), я использовал:
xmllint --format ./myxmlfile.xml
Попробуйте:
xmllint --xpath '//tag[position()<=2]' file.xml
Или:
xmlstarlet sel -t -c '//tag[position()<=2]' file.xml
Или:
xmlstarlet sel -t -m '//tag[position()<=2]' -c . -n file.xml
Если бы вы хотели сделать это с помощью sed
, только вы могли сделайте что-нибудь вроде:
sed -n '
1{x;s/^/../;x;}; # initialise counter with two tokens
/<tag>/,/<\/tag>/ {
p; /<\/tag>/{
x;s/.//;/./!q;x; # remove a token and quit if hold space empty
}
}' file.xml
То есть используйте удерживаемое пространство в качестве счетчика оставшихся отображаемых разделов (используя символы точки).