Ваши задачи на SED

Приветствую!


Важно: тема переехала в форум


Придумал такую идею:

Вы присылайте мне свои задачки по SED-у, а я, по мере возможности, буду их решать и выкладывать здесь.
Вам решение задачи, а мне зарядка - все довольны! :)

Короче говоря: присылайте задачи, связанные с SED-ом!

Мне будет гораздо интереснее решать задачу, если буду знать для чего она... ;)

Теги: 

Комментарии

Задача: Убить процесс (kill -9) по имени (изобретение велосипеда) 
1) ps aux (получаем список процессов, в таблице)
2) Вывод
1) обрабатываем sed'ом: sed -n '/$processname/p' (получаем соответствующие строки)
Например:
User 17453 0.5 3.8 298536 38932 ? Sl 10:21 0:04 processname
User 17459 0.5 3.8 298536 38932 ? Sl 10:21 0:04 processname
User 99999 0.5 3.8 298536 38932 ? Sl 10:21 0:04 processname 
3) Вывод
2) надо обработать, чтобы получить число из 2-го столбца ($process_ID) sed -n '???'
4) Убиваем порцессы: kill -9 $process_ID

Решение (одним конвеером):
kill -9 $(ps aux| sed -ne '/$processname/ s/[^ ]*[ ]*\([^ ]*\).*/\1/p')

Единственный минус -- процесс, рождаемый SED'ом тоже отображается и пытается kill-нуть. Буду искать.

Не совсем понял задачи этого велосипеда!?

Чем плох "killall -9 processname"?

Если честно, забыл... Так как почти не использовал... Использовал kill, top, ps aux, etc.

Бывает :)

Продублирую сюда на всякий случай :)
Необходимо сделать вывод событий из google calendar в одну строку (для conky). Использую gcalcli, пока получилось вот что: gcalcli --tsv agenda | awk -F"\t" '{split($1,date,"-"); print date[3]"."date[2]"."date[1] ": " $4}' | head -n 5 | sed ':a;N;$!ba;s/\n/; /g'
Беру дату события и текст события из gcalcli, преобразую, получаю что-то типа:
29.10.2012: Событие1
29.10.2012: Событие2
30.10.2012: Событие3
30.10.2012: Событие4
01.11.2012: Событие5
В моем приведенном примере последний кусок (нашел на просторах сети) sed ':a;N;$!ba;s/\n/; /g' преобразует вышеописанное к виду
29.10.2012: Событие1; 29.10.2012: Событие2; 30.10.2012: Событие3; 30.10.2012: Событие4; 01.11.2012: Событие5
Хочу убрать дублирующие даты, т.е. привести к виду: 29.10.2012: Событие1, Событие2; 30.10.2012: Событие3, Событие4; 01.11.2012: Событие5
Что дописать к sed в конце?

Сколько голову не ломал, никак не могу это чистым седом победить... Очень не хватает в нем своих переменных.
Логика при обработке строки, достаточно проста: "Если дата изменилась, то вывожу ее, иначе дату не вывожу"
Но сед не может "запомнить значение и выполнить сравнение с этим значением"... :( Или я просто не знаю как... :)

Данную задачу достаточно легко можно победить, если использовать sh или awk! Интересует? :)

Да, интересует решение через awk.

Ну тогда для данных:

29.10.2012: Событие1
29.10.2012: Событие2
30.10.2012: Событие3
30.10.2012: Событие4
01.11.2012: Событие5

Подойдет следующий код:

awk '{if (var == $1) {printf ("%s", $2)} else {var = $1; printf ("\n%s ", $0)}}'
В результате:

29.10.2012: Событие1 Событие2
30.10.2012: Событие3 Событие4
01.11.2012: Событие5

Здравствуй Антон,

помоги, пожалуйта, решить небольшую задачу,

есть очень большой текстовый файл, кусок в приложении.

Задача выбрать все строки стоящие перед текстовыми комментариями.

то есть, из вот такого:

00:31:56 DATA 1 350 5499.0 1585.0 8
00:43:14 DATA 1 351 5505.0 1585.0 8
00:47:59 DATA 1 352 5505.0 1584.0 8
brak
00:51:20 DATA 1 353 5505.0 1584.0 8
n/s
00:56:44 DATA 1 354 5505.0 1583.0 8
01:01:22 DATA 1 355 5505.0 1582.0 8
01:06:23 DATA 1 356 5505.0 1581.0 8
01:10:15 DATA 1 357 5505.0 1580.0 8

мне нужны только
00:47:59 DATA 1 352 5505.0 1584.0 8
00:51:20 DATA 1 353 5505.0 1584.0 8

комментарии могут содержать больше одной строки, все строки с данными начинаются c даты, вида 00:51:20

Буду признателен.

Решение:
#!/bin/sed -nf
:a #Метка начала
h #Беру текущую строку и помещаю ее в буфер
n #Перехожу к след. строке
/^[^0-9]\{2\}/{ #Если эта "след. строка" является комментарем, то вывожу строку из буфера (можно задать более жесткое условие, а не только 2 числа в начале строки)
        g #Замещаю текущую строку, строкой из буфера
        p #Печатаю текущую строку
}
ba #Перейти начало

Ну и тоже самое в одну строку: sed -n ':a;h;n;/^[^0-9]\{2\}/{g;p};ba' data.txt

Выдёргиваю информацию по шаблону connected (expired), получаю следующее

connected (expired)
127.0.0.1
--
connected (expired)
127.0.0.2
--
connected (expired)
127.0.0.3
Теперь мне необходимо выдернуть из строк IP и преобразовать его в такой вид, записав в файл

NAT:127.0.0.1-127.0.0.1
Используется это с целью занести клиентов с законченной подпиской в файервор

Вариант раз:

cat file|sed -n '/connected (expired)/{n;s/.*/NAT:\0-\0/p}'

Вариант два:

cat file|sed -n '/connected (expired)/{n;s/\([0-9.]\{2,4\}[0-9.]\{2,4\}[0-9.]\{2,4\}[0-9]\{1,3\}\)/NAT:\1-\1/p}'

Здесь вместо cat file, используйте свой конвейер, который выводит Ваш:

connected (expired)
127.0.0.1
--
connected (expired)
127.0.0.2
--
connected (expired)
127.0.0.3

Бьюсь над тривиальной задачей, нужно
отделить имя хоста от полного доменного
имени, т.е. имеем war.lan.net.ua , нужен только
war(лишнее отсечь).
Заранее оч. благодарен!!!

SED:
echo "war.lan.net.ua"|sed 's/\([^\.]*\).*/\1/'
echo "war.lan.net.ua"|sed 's/^\([0-9a-z_-]*\).*/\1/'
AWK:
echo "war.lan.net.ua"|awk 'BEGIN{FS="."}{print $1}'

Привет, я новичек в Линуксе, только начинаю писать скрипты и мне нужна помощь. Иногда бывает нужно распаковать множество архивив (которые бывают совсем разные, например 7z, zip, rar, cbr, chm, tar, tgz, gzip, bzip), каждый в свои каталоги. Я пользуюсь программой 7zip, которая сама не умеет создавать каталоги для архивов, поэтому я беру имена каталогов из имен файлов. Чтобы не нагромождать скрипт, я подумал про sed. Всего то надо удалить из строки последние символы до точки. Проблема в том, что расширений может быть несколько, но удалять надо только последнее.

Немного погуглив, нашел такую команду: sed 's/\(.*\)\..*/\1/'. Делает именно то, что и должна делать: удаляет символы от последней точки до конца строки. Но было бы интересно, если кто-нибудь пояснил бы структуру этой команды, т.к. для меня это выглядит как колдовство (надеюсь, со временем я и сам научусь такие команды на лету писать).

Поясняю по sed 's/\(.*\)\..*/\1/':

  • s - начинаю поиск
  • \(.*\) - ищу все подряд и запоминаю что нашел
  • \. - ищу последнюю точку (предыдущие точки съела предыдущая команда)
  • .* - ищи все подряд (после последней точки, см. предыдущую команду), но уже не запоминаю что нашел
  • \1 - заменяю все найденное запомненным значением (см. первую команду в скобочках).

Т.е. ищу все, кроме последней точки и всякого мусора за ней (кроме этой последней точки, при этом мусор может быть, а может и не быть) и заменяю все тем, что нашел.

Спасибо за ответ, но все-же хочу уточнить пояснения на примере war.lan.net.ua:

  1. s - начинаю поиск (тут все ясно)
  2. \(.*\)\. - ищу все подряд и запоминаю что нашел до последней точки (последняя точка берется из-за жадности рег.выражений, в результате запоминаем "war.lan.net", точка не запоминается, потому-что за скобками)
  3. .* - ищу дальше все подряд, но уже не запоминаю что нашел (учитывая точку из предыдущей команды, получим ".ua")
  4. \1 - заменяю все найденное запомненным значением (см. первую команду в скобочках).

Я слабо разбираюсь в синтаксисе команды sed (вернее, в регулярных выражениях), но по логике должно быть так, как у меня описано.
Кстати, жадность можно ограничить. Например, изменив команду в скобках на \(.*?\)\., получим только "war". (*? — 0 или больше, не жадный поиск, +? — 1 или больше, не жадный поиск).

Почти все верно поняли, кроме жадного и не жадного поиска. 
Для эксперимента рекомендую выполнить свою команду и увидеть что получится! :)

Чтобы выделить только первую часть доменного имени, надо выполнить следующую команду:

echo "war.lan.net.ua"|sed 's/\([^\.]*\).*/\1/'

Здесь:

  • [^\.]* - подходят все знаки, кроме точки
  • .* - всё остальное

Также не совсем верно ".* - ищу дальше все подряд, но уже не запоминаю что нашел (учитывая точку из предыдущей команды, получим ".ua")" - здесь полчится не .ua а ua!
Ведь точка съелась командой \. - это важно для понимания!
 

Иногда бывает нужно получить результат sed в виде переменной. Вот тут небольшой трюк, который показывает, как это сделать:
arg='war.lan.net'
x=$(echo $arg|sed 's/\(.*\)\..*/\1/')
echo $x

Добавлю только, что этот пример работает на Дебиане в оболочке bash, как он будет вести себя в других системах, я не знаю.

Добрый есть есть куча конфиг. файлов с подсетями
в которых сети разделены на :
-----------------------------------------------
subnet 10.0.0.0 netmask 255.255.255.224 {
option subnet-mask 255.255.255.224;
option routers 10.0.0.1;
option ntp-servers 10.0.0.4, 10.0.0.10;
pool { range 10.0.0.0 10.0.0.2; }
-----------------------------------------------
Надо добавить в каждый из них строки
-----------------------------------------------
min-lease-time 600;
default-lease-time 600;
max-lease-time 600;
-----------------------------------------------
чтоб получилось так:
-----------------------------------------------
subnet 10.0.0.0 netmask 255.255.255.224 {
option subnet-mask 255.255.255.224;
option routers 10.0.0.1;
option ntp-servers 10.0.0.4, 10.0.0.10;
min-lease-time 600;
default-lease-time 600;
max-lease-time 600;
pool { range 10.0.0.0 10.0.0.2; }
-----------------------------------------------

Самый простой вариант:

cat file|sed '/pool/s/^/min-lease-time 600;\ndefault-lease-time 600;\nmax-lease-time 600;\n/'

Если нужно что-то еще учитывать, то дайте знать.

Класс!! а если допустим внутри файла подсети с subnet 10.168.0.0 netmask 255.255.255.224 по subnet 10.168.8.224 netmask 255.255.255.224
а мне нужно допустим добавить только с 10.168.0.0 netmask 255.255.255.224 по subnet 10.168.6.224 netmask 255.255.255.224

Тогда будет несколько по-сложнее:

for i in 1 2 3 4 5 6;do sed -i "/subnet 10.168.$i.0/!b;:x;n;/pool/s/^/min-lease-time 600;\ndefault-lease-time 600;\nmax-lease-time 600;\n/;t;/\}/b;bx" file;done

Здесь sed-конструкция вызывается несколько раз, для каждой подсети.

Поясняю:

  • for i in 0 1 2 3 4 5 6 - перебираю цифры от 0 до 6. Можно заменить на for i in `seq 0 6`
  • /subnet 10.168.$i.0/!b - прерываю обработку текущей строки, пока не найду нужную мне подсеть (с учетом подстановки $i)
  • :x - метка, для перехода (аналог меток для GOTO во многих языках программирования)
  • n - беру на анализ следующую строку (текущая - это начало описания подсети).
  • /pool/s/^/min-lease-time 600;\ndefault-lease-time 600;\nmax-lease-time 600;\n/ - конструкция из примера выше
  • t - если успешно нашел pool и подставил нужные значения, то выхожу из sed
  • /\}/b - прерываю обработку, если найду закрывающийся тег } раньше блока pool (на случай, если в блоке подсети нет секции pool)..
  • bx - никакие из условий пока не выполнились, по-этому перехожу на метку x

Рекомендую почитать мою же статью Редактирование /etc/network/interfaces в Debian средствами sed - там есть много подобного.

Примечание: при тестах, bash может не проглотить данный скрипт, потому-что делает много интерактивных подстановок (в частности не нравится ему восклицательный знак). Может понадобиться временно отключить эту его функцию:set +H. Если встроить этот код в скрипт, то все будет работать и так.

Здравствуйте.Подскажите пожалуйста, как вывести из файла fstab раздел диска и его партицию? С помошью sed или awk.

Не совсем понял задачу, ибо в чем различие между разделом диска и партицией?...

Понял как: вывести раздел диска (партицию) и точку монтирования этого раздела диска (целого диска или RAIDа):

awk '{print $1 " " $2}' /etc/fstab |grep -e "^#" -v

Здесь awk выводит первый и второй столбец, а grep удаляет закомментеные строки (если они есть).

Если я ошибся в понимании задачи, то поправьте, пожалуйста.
:)

Привет! в файле printer.conf который находится в директории /etc/cups/, нужно добавить слово papercut: перед словом socket. я мало разбираюсь в bash, у меня получилось примерно так

cat /etc/cups/printer.conf | sed 's/socket/paperercut:socket/g'
, только файл не сохраняется.. помогите пожалуйста.

sed -i 's/socket/paperercut:socket/g' /etc/cups/printer.conf

:))

Добрый день,
Файл содержит более 1 млн строк. Необходимая информация находится в первых 20 строках. Как указать sed'у выполнять поиск в первых 20 строках?

Два варианта:

  1. sed '21q;s/aa/bb/g' file
  2. Использовать head в конвейере до вызова седа.

 

В выходной файл были помещены только 21 строка. А как туда поместить остальные?

sed '1,20s/aa/bb/g' file

Спасибо

Всегда пожалуйста! :)

Здравствуйте
есть Doxyfile в котором в некоторых переменных нужно поменять пути к каталогам:
Например:
OUTPUT_DIRECTORY = /old/path/name/doc
поменять на
OUTPUT_DIRECTORY = /some/thing/new/path/doc
Все усложняется использованием в sed-скрипте переменных оболочки

Не совсем понял в чем трудность?..

Например:

export a="/old/path/name/doc"
export b="/some/thing/new/path/doc"
echo "OUTPUT_DIRECTORY = /old/path/name/doc"|sed "s;$a;$b;g"
OUTPUT_DIRECTORY = /some/thing/new/path/doc

Здесь оболочка bash сперва подставляет переменные a и b в двойные кавычки, а уже после передает все это седу.
Ну а чтобы слеши в путях обрабатывались седом как надо, вместо слешей седа используется символ ;

Если я неправильно Вас понял, то направьте в нужную сторону. :)

Я наверное не совсем точно выразился, попробую еще раз.
Надо в строках вида:
ПЕРЕМЕННАЯ = ЗНАЧЕНИЕ
поменять значение ЗНАЧЕНИЯ ;-) на значение взятое из переменной
Я использую что-то вроде

sed -e "s@VAR_NAME = .*@VAR_NAME = $VAR_VALUE@" -i my_file

Есть ли вариант попроще?

С регулярными выражениями только начал разбираться:
Новый вариант:

sed -e "s@\(VAR_NAME = \).*@\1$VAR_VALUE@" -i my_file

т.е., то что нашли в \( ... \) - запоминаем, а в подстановке вставляем запомненное

Будет проще понять, если покажите кусок файла, с которым работаете.
И кусок файла, который хотелось бы получить, на основе первого.

Перловую конструкцию записать на sed

$a='Поручика мама очень любила. Мама мыла раму.';
$a =~ m/мама (.*) \w*/i;
print $1;

Если бы я еще настолько знал Перл... :) Какая логика работы этой программы?

есть файлик вида:
Термин
Описание
Термин2
Описание2

Впереди описания пробел
пытаюсь сделать вида
Термин; Описание
Термин2; Описание2

пробую вида
sed -e '/^[^ ]/{h;n;g;p }' 1.txt

не выходит :(

Если я правильно понял логику:
Если текущая строка - это описание (т.е. НЕ начинается с пробела), то беру следующую строку и добавляю ее к текущей (добавив ; между ними).
Если так, тогда код выглядит следующим образом:

# cat 1.txt 
Термин
 Описание
Термин2
 Описание2
 
# sed '/^[^ ]/{N;s/\n/;/}' 1.txt
Термин; Описание
Термин2; Описание2

Побольше бы информации по данной темке, интересно ж однако ознакомиться

А что конкретно вас интересует?...