15 заметок с тегом


awk oneliners

14 февраля 2017, 16:58

Постоянно забываю синтаксисы sed и awk. Нашёл для awk очень актуальный список однострочников:

HANDY ONE-LINE SCRIPTS FOR AWK                               30 April 2008
Compiled by Eric Pement — eric [at] pement.org               version 0.27

Latest version of this file (in English) is usually at:

This file will also be available in other languages:
   Chinese  — http://ximix.org/translation/awk1line_zh-CN.txt   


   Unix: awk '/pattern/ {print «$1»}'    # standard Unix shells
DOS/Win: awk '/pattern/ {print «$1»}'    # compiled with DJGPP, Cygwin
         awk «/pattern/ {print \»$1\«}»  # GnuWin32, UnxUtils, Mingw

Note that the DJGPP compilation (for DOS or Windows-32) permits an awk
script to follow Unix quoting syntax '/like/ {«this»}'. HOWEVER, if the
command interpreter is CMD.EXE or COMMAND.COM, single quotes will not
protect the redirection arrows (<, >) nor do they protect pipes (|).
These are special symbols which require «double quotes» to protect them
from interpretation as operating system directives. If the command
interpreter is bash, ksh or another Unix shell, then single and double
quotes will follow the standard Unix usage.

Users of MS-DOS or Microsoft Windows must remember that the percent
sign (%) is used to indicate environment variables, so this symbol must
be doubled (%%) to yield a single percent sign visible to awk.

If a script will not need to be quoted in Unix, DOS, or CMD, then I
normally omit the quote marks. If an example is peculiar to GNU awk,
the command 'gawk' will be used. Please notify me if you find errors or
new commands to add to this list (total length under 65 characters). I
usually try to put the shortest script first. To conserve space, I
normally use '1' instead of '{print}' to print each line. Either one
will work.


 # double space a file
 awk '1;{print «»}'
 awk 'BEGIN{ORS=«\n\n»};1'

 # double space a file which already has blank lines in it. Output file
 # should contain no more than one blank line between lines of text.
 # NOTE: On Unix systems, DOS lines which have only CRLF (\r\n) are
 # often treated as non-blank, and thus 'NF' alone will return TRUE.
 awk 'NF{print $0 «\n»}'

 # triple space a file
 awk '1;{print «\n»}'


 # precede each line by its line number FOR THAT FILE (left alignment).
 # Using a tab (\t) instead of space will preserve margins.
 awk '{print FNR «\t» $0}' files*

 # precede each line by its line number FOR ALL FILES TOGETHER, with tab.
 awk '{print NR «\t» $0}' files*

 # number each line of a file (number on left, right-aligned)
 # Double the percent signs if typing from the DOS command prompt.
 awk '{printf(«%5d : %s\n», NR,$0)}'

 # number each line of file, but only print numbers if line is not blank
 # Remember caveats about Unix treatment of \r (mentioned above)
 awk 'NF{$0=++a « :» $0};1'
 awk '{print (NF? ++a « :» :«») $0}'

 # count lines (emulates «wc -l»)
 awk 'END{print NR}'

 # print the sums of the fields of every line
 awk '{s=0; for (i=1; i<=NF; i++) s=s+$i; print s}'

 # add all fields in all lines and print the sum
 awk '{for (i=1; i<=NF; i++) s=s+$i}; END{print s}'

 # print every line after replacing each field with its absolute value
 awk '{for (i=1; i<=NF; i++) if ($i < 0) $i = -$i; print }'
 awk '{for (i=1; i<=NF; i++) $i = ($i < 0) ? -$i : $i; print }'

 # print the total number of fields ("words") in all lines
 awk '{ total = total + NF }; END {print total}' file

 # print the total number of lines that contain "Beth"
 awk '/Beth/{n++}; END {print n+0}' file

 # print the largest first field and the line that contains it
 # Intended for finding the longest string in field #1
 awk '$1 > max {max=$1; maxline=$0}; END{ print max, maxline}'

 # print the number of fields in each line, followed by the line
 awk '{ print NF «:» $0 } '

 # print the last field of each line
 awk '{ print $NF }'

 # print the last field of the last line
 awk '{ field = $NF }; END{ print field }'

 # print every line with more than 4 fields
 awk 'NF > 4'

 # print every line where the value of the last field is > 4
 awk '$NF > 4'


 # create a string of a specific length (e.g., generate 513 spaces)
 awk 'BEGIN{while (a++<513) s=s " "; print s}'

 # insert a string of specific length at a certain character position
 # Example: insert 49 spaces after column #6 of each input line.
 gawk --re-interval 'BEGIN{while(a++<49)s=s " "};{sub(/^.{6}/,"&" s)};1'


 # These next 2 entries are not one-line scripts, but the technique
 # is so handy that it merits inclusion here.
 # create an array named "month", indexed by numbers, so that month[1]
 # is 'Jan', month[2] is 'Feb', month[3] is 'Mar' and so on.
 split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec", month, " ")

 # create an array named "mdigit", indexed by strings, so that
 # mdigit["Jan"] is 1, mdigit["Feb"] is 2, etc. Requires "month" array
 for (i=1; i<=12; i++) mdigit[month[i]] = i


 # IN UNIX ENVIRONMENT: convert DOS newlines (CR/LF) to Unix format
 awk '{sub(/\r$/,"")};1'   # assumes EACH line ends with Ctrl-M

 # IN UNIX ENVIRONMENT: convert Unix newlines (LF) to DOS format
 awk '{sub(/$/,"\r")};1'

 # IN DOS ENVIRONMENT: convert Unix newlines (LF) to DOS format
 awk 1

 # IN DOS ENVIRONMENT: convert DOS newlines (CR/LF) to Unix format
 # Cannot be done with DOS versions of awk, other than gawk:
 gawk -v BINMODE="w" '1' infile >outfile

 # Use «tr» instead.
 tr -d \r outfile            # GNU tr version 1.22 or higher

 # delete leading whitespace (spaces, tabs) from front of each line
 # aligns all text flush left
 awk '{sub(/^[ \t]+/, «»)};1'

 # delete trailing whitespace (spaces, tabs) from end of each line
 awk '{sub(/[ \t]+$/, «»)};1'

 # delete BOTH leading and trailing whitespace from each line
 awk '{gsub(/^[ \t]+|[ \t]+$/,«»)};1'
 awk '{$1=$1};1'           # also removes extra space between fields

 # insert 5 blank spaces at beginning of each line (make page offset)
 awk '{sub(/^/, «     „)};1'

 # align all text flush right on a 79-column width
 awk '{printf „%79s\n“, $0}' file*

 # center all text on a 79-character width
 awk '{l=length();s=int((79-l)/2); printf „%“(s+l)“s\n»,$0}' file*

 # substitute (find and replace) «foo» with «bar» on each line
 awk '{sub(/foo/,«bar»)}; 1'           # replace only 1st instance
 gawk '{$0=gensub(/foo/,«bar»,4)}; 1'  # replace only 4th instance
 awk '{gsub(/foo/,«bar»)}; 1'          # replace ALL instances in a line

 # substitute «foo» with «bar» ONLY for lines which contain «baz»
 awk '/baz/{gsub(/foo/, «bar»)}; 1'

 # substitute «foo» with «bar» EXCEPT for lines which contain «baz»
 awk '!/baz/{gsub(/foo/, «bar»)}; 1'

 # change «scarlet» or «ruby» or «puce» to «red»
 awk '{gsub(/scarlet|ruby|puce/, «red»)}; 1'

 # reverse order of lines (emulates «tac»)
 awk '{a[i++]=$0} END {for (j=i-1; j>=0;) print a[j—] }' file*

 # if a line ends with a backslash, append the next line to it (fails if
 # there are multiple lines ending with backslash...)
 awk '/\\$/ {sub(/\\$/,«»); getline t; print $0 t; next}; 1' file*

 # print and sort the login names of all users
 awk -F «:» '{print $1 | «sort» }' /etc/passwd

 # print the first 2 fields, in opposite order, of every line
 awk '{print $2, $1}' file

 # switch the first 2 fields of every line
 awk '{temp = $1; $1 = $2; $2 = temp}' file

 # print every line, deleting the second field of that line
 awk '{ $2 = «»; print }'

 # print in reverse order the fields of every line
 awk '{for (i=NF; i>0; i—) printf(«%s „,$i);print „“}' file

 # concatenate every 5 lines of input, using a comma separator
 # between fields
 awk 'ORS=NR%5?“,»:«\n»' file


 # print first 10 lines of file (emulates behavior of «head»)
 awk 'NR < 11'

 # print first line of file (emulates "head -1")
 awk 'NR>1{exit};1'

  # print the last 2 lines of a file (emulates «tail -2»)
 awk '{y=x «\n» $0; x=$0};END{print y}'

 # print the last line of a file (emulates «tail -1»)
 awk 'END{print}'

 # print only lines which match regular expression (emulates «grep»)
 awk '/regex/'

 # print only lines which do NOT match regex (emulates «grep -v»)
 awk '!/regex/'

 # print any line where field #5 is equal to «abc123»
 awk '$5 == «abc123»'

 # print only those lines where field #5 is NOT equal to «abc123»
 # This will also print lines which have less than 5 fields.
 awk '$5 != «abc123»'
 awk '!($5 == «abc123»)'

 # matching a field against a regular expression
 awk '$7  ~ /^[a-f]/'    # print line if field #7 matches regex
 awk '$7 !~ /^[a-f]/'    # print line if field #7 does NOT match regex

 # print the line immediately before a regex, but not the line
 # containing the regex
 awk '/regex/{print x};{x=$0}'
 awk '/regex/{print (NR==1 ? «match on line 1» : x)};{x=$0}'

 # print the line immediately after a regex, but not the line
 # containing the regex
 awk '/regex/{getline;print}'

 # grep for AAA and BBB and CCC (in any order on the same line)
 awk '/AAA/ && /BBB/ && /CCC/'

 # grep for AAA and BBB and CCC (in that order)
 awk '/AAA.*BBB.*CCC/'

 # print only lines of 65 characters or longer
 awk 'length > 64'

 # print only lines of less than 65 characters
 awk 'length < 64'

 # print section of file from regular expression to end of file
 awk '/regex/,0'
 awk '/regex/,EOF'

 # print section of file based on line numbers (lines 8-12, inclusive)
 awk 'NR==8,NR==12'

 # print line number 52
 awk 'NR==52'
 awk 'NR==52 {print;exit}'          # more efficient on large files

 # print section of file between two regular expressions (inclusive)
 awk '/Iowa/,/Montana/'             # case sensitive


 # delete ALL blank lines from a file (same as "grep '.' ")
 awk NF
 awk '/./'

 # remove duplicate, consecutive lines (emulates "uniq")
 awk 'a !~ $0; {a=$0}'

 # remove duplicate, nonconsecutive lines
 awk '!a[$0]++'                     # most concise script
 awk '!($0 in a){a[$0];print}'      # most efficient script


Special thanks to the late Peter S. Tillier (U.K.) for helping me with
the first release of this FAQ file, and to Daniel Jana, Yisu Dong, and
others for their suggestions and corrections.

For additional syntax instructions, including the way to apply editing
commands from a disk file instead of the command line, consult:

  "sed & awk, 2nd Edition," by Dale Dougherty and Arnold Robbins
  (O'Reilly, 1997)

  "UNIX Text Processing," by Dale Dougherty and Tim O'Reilly (Hayden
  Books, 1987)

  "GAWK: Effective awk Programming," 3d edition, by Arnold D. Robbins
  (O'Reilly, 2003) or at http://www.gnu.org/software/gawk/manual/

To fully exploit the power of awk, one must understand "regular
expressions." For detailed discussion of regular expressions, see
"Mastering Regular Expressions, 3d edition" by Jeffrey Friedl (O'Reilly,

The info and manual ("man") pages on Unix systems may be helpful (try
"man awk", "man nawk", "man gawk", "man regexp", or the section on
regular expressions in "man ed").

USE OF '\t' IN awk SCRIPTS: For clarity in documentation, I have used
'\t' to indicate a tab character (0x09) in the scripts.  All versions of
awk should recognize this abbreviation.

#---end of file---

pf и ipfw одновременно — последовательность обработки пакетов

29 октября 2015, 15:01

Вводная информация. Зачем нужны два файрвола.

Так исторически сложилось, что на ряде FreeBSD-серверов в конторе мы используем одновременно два файрвола. Иногда это связано с ограничением функционала, иногда — с багами в работе конкретного файрвола.

Например, на pf гораздо проще организовать NAT в несколько внешних IP, причём разбрасывать клиентов по IP он может разными алгоритмами. Мы используем source hash — когда файрвол выбирает IP, в который будет NATиться конкретный пользователь, исходя из IP-адреса этого пользователя. Т. е. в рамках одной PPPoE-сессии конкретный клиент всегда будет ходить с одного и того же IP.

Или, например, у pf есть возможность ограничивать количество соединений в секунду. Так мы блокируем активность ботов по рассылке спама на пользовательских компах — число соединений с одного пользовательского IP на любой 25-ый порт в интернете ограничено не более чем 10 соединений в 30 секунд. Если пользователь превышает этот лимит — его IP заносится в табличку и ему доступ к любому 25-му порту блокируется на 1 час. По истечении часа скрипт очищает эту табличку, и всё начинается заново.

Но есть у pf и проблемы. Например, у него до сих пор нет connection tracker'a для протокола PPTP. Что делает невозможным использование пользователями PPTP-туннелей. Точнее, не так. Пользователь может юзать PPTP-туннель, но это доступно будет только одному пользователю на конкретном BRAS'e. Соответственно — кто раньше туннель установил, того и тапки. У остальных туннели работать уже не будут — файрвол не в состоянии отличить идущие внутри туннеля пакеты и правильно раскидать их по пользователям. Поэтому задачу NAT'a PPTP и GRE трафика мы решаем с помощью IPFW.

Также в процессе эксплуатации в файрволе pf выявилось несколько багов. Про один я уже писал, про второй написать пока только планирую. Коротко — суть бага в том, что при использовании в pf правила route-to для пакетов, пришедших через PPPoE и прошедших NAT криво просчитывается контрольная сумма. Старший и младший байт контрольной суммы меняются местами (т. е. если сумма должна быть 0xABDC, то в результирующем пакете после обработки его PF контрольная сумма проставлялась 0xCDAB), и получатель такого пакета его просто молча дропает. Поэтому этот функционал также пришлось запилить на IPFW.

К чему я это всё?

А вот к чему. Когда используешь два файрвола одновременно, то возникает вопрос последовательности обработки пакетов файрволами, чтобы правила одного файрвола не мешали работать правилам другого файрвола. Поэтому в какой-то момент пришлось разбираться с тем, как всё это работает.

Как это работает

Было бы логично предположить, что последовательность обработки пакетов двумя файрволами будет зависеть от последовательности загрузки соответствующих модулей ядра. Но, как говорит наш школьный физик: «Мысль хорошая. Но неправильная». На самом деле, последовательность обработки будет зависеть от того, в какой последовательности файрволы встраивают свои обработчики (hooks) в цепочки обработчиков фреймворка pfil (подробности можно почитать в man 9 pfil). Эти цепочки представляют собой очереди, отдельно для обработки входящих пакетов, и отдельно — для исходящих.

Причём каждый файрвол вставляет свой обработчик в цепочку в разные моменты своей загрузки. IPFW вставляет сразу при загрузке модуля, а pf при загрузке модуля ничего не делает, и встраивает обработчик только когда мы говорим pfctl -e, т. е. собственно включаем файрвол.

Соответственно, если посмотреть на rcoderd, то файрволы грузятся в следующем порядке:

# rcorder /etc/rc.d/* | grep -nE '/i?pfw?$'

Соответственно, в этом случае обработка будет происходить так:

Как следует из картинки, при стандартной схеме загрузки файрволов для входящих пакетов первым будет отрабатывать IPFW, а для исходящих первым будет работать PF.

Также нужно понимать, что если вы перезапустите файрвол pf, то последовательность обработки поменяется, так как в момент останова pf уберёт обработчики из очереди, а при запуске опять вставит их, но уже вот так:

Соответственно, входящие пакеты первым будут попадать в PF, а исходящие — сначала обрабатываться IPFW.

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

При желании, можно изменить порядок начальной инициализации файрволов таким образом, чтобы IPFW стартовал первым. Для этого не обязательно править rcorder, достаточно в /boot/loader.conf вписать загрузку модуля IPFW. Пользуясь тем, что IPFW ставит свои обработчики при загрузке модуля, получится, что IPFW будет стартовать первым.

Зашифровать файл

31 июля 2014, 11:39

Задался вопросом, а как можно зашифровать отдельный файл на фре. Про geli или gbde всё ясно, а вот так, чтобы отдельный файл? Раньше шифровал с помощью rar — запихивал в зашифрованый архив. В свете того, что нужно на домашнем хранилище кое-что бэкапить, причём желательно так, чтобы «никто не нашёл», а лишние сущности плодить (сиречь ставить rar) не хочется, изыскал на просторах интернетов рецепт щастья шифрования отдельных файлов. Рецепт незамысловат — функцией шифрования обладает openssl. Чтобы зашифровать файл, нужно сделать раз:

openssl enc -aes-256-xts -in file.dat -out file.dat.enc

Шифрует файл file.dat с помощью алгоритма AES-256 и сохраняет шифрованный файл в файле file.dat.enc

Чтобы расшифровать, делаем два:

openssl enc -d -aes-256-xts -in file.dat.enc -out file.dat

Ещё у openssl enc есть прикольная функция — оно умеет преобразовывать бинарники в base64. Делается это с помощью ключа -a. Например, рассмотрим ситуацию, когда надо передать бинарник, а воспользоваться scp или подобными утилитами нет возможности. Тогда можно сделать так:

openssl enc -a -in file.dat -out file.dat.base64

И получившийся в результате file.dat.base64 можно просто передать хоть прямо через буфер между двумя терминалами, а на принимаемой стороне потом сделать:

openssl enc -a -d -in file.dat.base64 -out file.dat

Минимальное ядро и загрузка с ZFS

22 ноября 2013, 18:06

Попытался тут оторвать все модули и жёстко впаять необходимое в ядро. Система грузится с ZFS, а также используется AIO для самбы, ибо сильно увеличивает скорость работы самбовых шар. Проверил как-то ради шутки — без AIO гиговый файл копировался 9 минут 10 секунд, с включенным AIO — 1 минуту 45 секунд. Разница примерно 6 раз.

В общем, пока (на момент версии FreeBSD 9.2) выясняется, что zfs.ko и opensolaris.ko в ядро не включить, можно только модулями ядра грузить. Добавил в ядро только AIO:

options VFS_AIO

И прописал в make.conf:

MODULES_OVERRIDE=zfs opensolaris

Собрал ядро, установил, попытался перезагрузиться и получил облом. При загрузке система стала утверждать, что не знает, откуда смонтировать root. Оказывается, нужен ещё модуль krpc.ko, отвечающий за реализацию RPC в солярке (откуда и портирована ZFS). Причём судя по обрывочным сведениям, нужен он только на 64-битной фре, на 32-битной вроде как нет. Проверять, честно говоря, влом :-)

Правим make.conf:

MODULES_OVERRIDE=zfs opensolaris krpc

Пересобираем ядро, ставим, перезагружаемся — и всё взлетает нормально.

Удалённое выполнение команд по ssh

20 ноября 2013, 16:57

В свете полученных знаний из ссылок в предыдущем посте решил начать жить по-новому. А именно — бэкапные файлы с фри на фрю складывать не по SMB или FTP, как раньше, а по scp или ssh. Подумал, и решил, что удобнее будет через ssh — туда загоняется поток через pipe, а на удалённой стороне из pipe складывается в локальнный (для удалённого сервера-хранилища бэкапов) файл. Проверил даже скорость — в конкретной сети гиговый файл через SMB скопировался за  1 минуту 48 секунд, а то же самое, но через ssh — за 1 минуту 35 секунд. Профит налицо :-)

Запустил mcedit и начал ваять. Со скриптом бэкапа системы проблем не возникло:

# cat system_backup.sh

DATE=`date +%Y.%m.%d`

dump -0 -L -u -C 32 -f — / | gzip -5 | ssh -q -i ${keyfile} ${username} "cat > ${backup_dir}/${DATE}_root.dump.gz"
dump -0 -L -u -C 32 -f — /usr | gzip -5 | ssh -q -i ${keyfile} ${username} "cat > ${backup_dir}/${DATE}_usr.dump.gz"
dump -0 -L -u -C 32 -f — /var | gzip -5 | ssh -q -i ${keyfile} ${username} "cat > ${backup_dir}/${DATE}_var.dump.gz"
dump -0 -L -u -C 32 -f — /home | gzip -5 | ssh -q -i ${keyfile} ${username} "cat > ${backup_dir}/${DATE}_home.dump."

keyfile — это private key для авторизации на удалённом сервере, backup_operator — имя пользователя на том сервере, а backup-ttr — имя самого сервера, где у нас бэкапы хранятся. Получается следующее — dump дампит раздел в stdout, откуда это перегоняется в gzip через pipe, откуда перегоняется вот в эту конструкцию:

ssh -q -i ${keyfile} ${username} "cat > ${backup_dir}/${DATE}_root.dump.gz"

Т. е. мы подключаемся к серверу backup-ttr, и выполняем там команду "cat > {backup_dir}/${DATE}_root.dump.gz". Которая читает из stdin сжатый gzip'ом и прокинутый по сети поток и сохраняет его в файл {backup_dir}/${DATE}_root.dump.gz.

В общем, тут всё просто. А вот с бэкапом почты пришлось повозиться. Было принято решение не просто каждый день сваливать полный бэкап почтовой базы и удалять старые копии, а сделать инкрементный бэкап. А для этого нужно производить определённые манипуляции на стороне хранилища бэкапов. Можно, конечно, сделать второй скрипт, и запускать его по крону на стороне хранилища. Но «Шурик, это же не наш метод!». Это придётся контролировать две точки выполнения одной операции — бэкапа почты, так как задействованы будут два скрипта, два крона, два компа и т. д.

Суть задачи такова. Допустим, у нас в день 1 выполняется полный бэкап почтовой базы. В день 2 — только то, что изменилось или добавилось со времени исполнения 1-го бэкапа, отработавшего в день 1. В день 3 — только изменения с момента бэкапа в день 2, и так далее до конца недели. Допустим, за первую неделю у нас сделано 7 архивов, с именами, соответственно, mail1.tgz, mail2.tgz, ... mail7.tgz. Где mail1.tgz — это полная копия базы, а остальные — дельты изменений. Неделя закончилась, надо как-то сделать ротацию бэкапов. То есть, куда-то деть эти 7 архивов. Можно их тупо удалить. Но как-то хочется, чтобы у нас хотя бы двухнедельная история бэкапов хранилась. Самое простое — эти архивы переименовать. Например, по схеме mail1.tgz.bak, mail2.tgz.bak и так далее. Тут-то и началось самое интересное.

Сначала решил сделать как-нибудь так:

for i in `ssh -q -i ${keyfile} ${username} "ls ${backup_dir}/*.gz"`
ssh -q -i ${keyfile} ${username} "mv ${i} ${i}.bak"

Но это получается 1 вызов ssh для получения списка файлов, и потом ещё в цикле 7 раз запускается ssh, чтобы переименовать каждый отдельный файл. Касичок кагбэ, неоптимальненко.

Камрады в интернете посоветовали сделать как-нибудь так:

ssh -q user@remoteserver 'ls -f /path/*.tgz | while read f; do "$"» };done'

Я попробовал, не прокатило. Полез искать точный синтаксис однострочного оформления циклов в shell. Нашёл несколько примеров, подставил, но ни один не прокатил. Я так понял, что приведённые примеры — в основном для линуксового bash, а у меня фрёвая /bin/sh, поэтому и не прокатывает. Потом нашёл обрывочные сведения, что циклы типа for, foreach, while — «is not single-line friendly». Но коллективный разум мне сурово возразил, что я не прав.

Копаясь дальше, я обнаружил, что реально, однострочные конструкции типа

ls | while read f;do echo ${f};done

работают, будучи запущены в /bin/sh и локально. Но не работают удалённо. Потом до меня дошло, что локально-то я запустил /bin/sh, и в ней пускаю все эти конструкции. Но по дефолту у пользователя backup_operator оболочка прописана /bin/csh, и синтаксис там может отличаться.

Не вопрос, топаем на удалённый сервер и делаем там:

pw usermod backup_operator -s /bin/sh

Меняем, иными словами, ему оболочку на sh вместо csh. После этого конструкция

ssh -i keyfile.ppk backup_operator@backup-ttr "ls /home/ | while read f; do echo ${f}; done;"

просто обязана была заработать. Но не заработала... А написала, что f: Undefined variable.

В процессе дальнейшего общения с коллективным разумом выяснилось следующее:

The shell will expand variables in double-quotes, but not in single-quotes.

что если команду для удалённого исполнения заключать в двойные кавычки, то оболочка подставляет значение переменной вместо имени переменной. А в этой ситуации нужно, чтобы имя переменной передавалось как есть на дальнюю сторону, и значение вычислялось и использовалось уже там. Поэтому эту команду нужно заключать в ОДИНАРНЫЕ кавычки. То есть, вот так:

ssh -i keyfile.ppk backup_operator@backup-ttr 'ls /home/ | while read f; do echo ${f}; done;'

Но есть и другой путь — просто ставить backslash перед символом переменной. То есть, можно сделать вот так:

ssh -i keyfile.ppk backup_operator@backup-ttr "ls /home/ | while read f; do echo \${f}; done;'

Мне так пришлось сделать, потому что по итогу потребовалось, чтобы в одной команде были и локальные и удалённые переменные. Т. е. финальная рабочая конструкция выглядит вот так:

ssh -i keyfile.ppk backup_operator@backup-ttr "cd ${backup_dir}; ls ./ | while read f; do mv \${f} \${f}.bak; done"

SSH tips&tricks

12 ноября 2013, 23:24

Обнаружил тут ряд интересных статей про использование ssh и screen. Оказывается, SSH умеет сильно больше, чем я подозревал.

Про SSH:

Про screen:

Ребилд RAID-5

7 ноября 2013, 17:28

Внезапно сервак стал жаловаться на здоровье, а именно — на то, что по данным SMART одного из дисков, стало этому диску плохеть. Диск у нас этот трудится в HP Proliant DL380 G5, в RAID-5, собранном на контроллере Compaq SmartArray P400.

Ну, винты на замену были, но диск пока вроде работает. Решили подоткнуть новый диск и пометить его как hot spare, на случай если диск из массива самозапилится, то будет подхвачен новый HS-диск. Диск подоткнули, но вот с hot spare получился облом — оказывается, этот контроллер умеет в массив вставлять HS только в момент создания. А в уже существующий массив — нет.

Так что надо менять диск. Я старательно всё забэкапил, тщательно прицелился, из какого отсека диск вынимать, запалил там лампочку-индикатор, и товарищ, находящийся на месте, диск заменил. Всё вроде понялось, спросило у меня «тут это, новый диск. Будем ребилдить массив?» Я грю — конечно, бро, надо ребилдить! И процесс пошёл. Поскольку на сервере стоит FreeBSD, а там особо никаких утилит нет, позволяющих получить кровавые подробности о состоянии массива, то удовольствоваться пришлось командой:

# camcontrol devlist
<COMPAQ RAID 5 VOLUME reco> at scbus0 target 0 lun 0 (da0,pass0)
<TEAC DV-W28E-RW G.B1> at scbus2 target 0 lun 0 (pass1,cd0)

Я так полагаю, что VOLUME reco должно индицировать, что volume recovering. Ну, сидим курим, ждём окончания ребилда. Час, два ждём... И тут кончается рабочий день.

В общем, на следующий день к часу дня статус массива так и не изменился. Тут-то я и насторожился. И обратился к коллективному разуму с вопросом — а нормально ли это, для 150-гигового винта в RAID-5 такое время ребилда? Коллективный разум однозначно решил, что ненормально, но посоветовал использовать утилиту sysutils/cciss_vol_status, каковую я немедленно и проинсталлировал. Утилита английским по белому сказала:

cciss_vol_status -V /dev/ciss0
Controller: Smart Array P400
Board ID: 0x3234103c
Logical drives: 1
Running firmware: 7.18
ROM firmware: 7.18
/dev/ciss0: (Smart Array P400) RAID 5 Volume 0 status: OK.
Physical drives: 7
connector 1I box 1 bay 7 HP DG146BABCF BS05P8708AUE0827 HPD6 OK
connector 1I box 1 bay 6 HP DG146ABAB4 3NM14RAG00009821Q0QZ HPDD OK
connector 1I box 1 bay 5 HP DG146BABCF BS05P86088T90827 HPD6 OK
connector 2I box 1 bay 4 HP DG146BABCF BS05P8608A890827 HPD6 OK
connector 2I box 1 bay 3 HP DG146BABCF BS05P8607UYT0826 HPD6 OK
connector 2I box 1 bay 2 HP DG146ABAB4 3NM15CQ500009822WG3Q HPDA OK
connector 2I box 1 bay 1 HP DG146BABCF BS05P8607V070826 HPD6 OK

То есть, она считает, что массив уже вполне ОК. Но это расходится с показаниями camcontrol. В общем, похоже, camcontrol не перечитал данные с контроллера. Почему-то. Пришлось ему принудительно сделать

#camcontrol rescan /dev/ciss0

Только после этого camcontrol ответил, что

<COMPAQ RAID 5 VOLUME OK> at scbus0 target 0 lun 0 (da0,pass0)

Вот и думай теперь, как ему после этого доверять. Придётся, видимо, в мониторинге переделывать получение данных о состоянии массива с camcontrol на cciss_vol_status :-(


1 ноября 2013, 21:36

Экспериментирую сейчас со сжатием в zfs. Насоздавал пачку файлов, где-то в районе 100 тысяч штук. И косанул в команде создания, они создались не в том каталоге, в котором планировалось. В итоге нужно перенести 100 тысяч файлов в другой каталог. Команда mv ./xaaa* /storage/mail/ мне отвечает:

/bin/mv: Argument list too long.

Полез копаться, и выяснилось, что оказывается, оболочка раскрывает wildcard *, просто заменяя её на имена подходящих под шаблон файлов и подставляя в реальную команду. В итоге получалось, что командная строка для mv была нефиговой такой длины. В качестве решения предлагали удалять файло через find :-) Я сделал так: find ./ -type f -exec mv {} /storage/mail \;

Хинт: разделить один файл (например, сгенерированный с помощью dd из /dev/urandom) на кучу мелких можно так:

split -b 1024 -a 10 /storage/mail/test.dat

Где «-b 1024» — размер одного файла, а «-a 10» — количество символов в имени минус 1 символ. Т. е. «-а 10» — будет генерировать файлы с именем длиной 11 символов.

И ещё накопал информацию о наличии некоторых проблем при удалении ОЧЕНЬ БОЛЬШОГО количества файлов (порядка нескольких миллионов и более). Там может не помочь даже использование find — всё потихоньку затормаживается и виснет. Внезапно (tm) выясняется, следующее:

find, как и ls, работают через библиотеку fts(3), в которой есть баг — если ей обрабатывать большие каталоги и код работает от рута, она сильно жрет память и CPU. А если код работает не от рута, то не жрет и делает всё то же самое (ну, если прав хватает).

Хранение серверных конфигов в SVN

13 декабря 2012, 17:11

Настраивал хранение freebsd-шных конфигов в SVN. Обнаружилось небольшое западло. Маааленькое такое. В результате которого если ты закрыл консоль, то больше к серваку не подключишься :-)

Причиной является то, что при операции svn commit после добавления всех конфигов в репозиторий, svn делает меняет права на конфигурационные файлы. В частности, добавляет права на чтение для ssh-ключей хоста сервера. Вот для этих файлов, иными словами: /etc/ssh_host_dsa_key, /etc/ssh_host_rsa_key и /etc/ssh_host_ecdsa_key. После чего система отказывается принимать новые соединения по ssh, мотивируя это тем, что:

Permissions 0644 for '/etc/ssh/ssh_host_rsa_key' are too open.
It is recommended that your private key files are NOT accessible by others.
This private key will be ignored.
bad permissions: ignore key: /etc/ssh/ssh_host_rsa_key
Could not load host key: /etc/ssh/ssh_host_rsa_key
Permissions 0644 for '/etc/ssh/ssh_host_dsa_key' are too open.
It is recommended that your private key files are NOT accessible by others.
This private key will be ignored.
bad permissions: ignore key: /etc/ssh/ssh_host_dsa_key
Could not load host key: /etc/ssh/ssh_host_dsa_key
Permissions 0644 for '/etc/ssh/ssh_host_ecdsa_key' are too open.
It is recommended that your private key files are NOT accessible by others.
This private key will be ignored.
bad permissions: ignore key: /etc/ssh/ssh_host_ecdsa_key
Could not load host key: /etc/ssh/ssh_host_ecdsa_key

И если своевременно не принять меры по устранению и недопущению, может получиться казус. Пока ssh-консоль открыта, есть некоторый шанс. Нужно вернуть права на ключи обратно, запретив чтение группе и другим. А в идеале — вернуть вообще все уехавшие права с файлов каталога /etc и всех подкаталогов, так как модификации прав подверглись все файлы.

Сделать это можно, воспользовавшись силой данного нам природой мозга и утилитой freebsd-update. У неё есть параметр IDS, который ревизует систему на предмет соответствия прав, структур каталогов и хэшей системных файлов тем, которые должны быть в системе искаропки. Нам же нужны только права доступа на файлы, а на хэши на текущем историческом этапе чхать. Поэтому делаем раз:

server# freebsd-update IDS | awk '/permissions/{ print $8" "$1 }' | xargs -t -L 1 chmod

Утиля долго думает, сверяется с Большим Братом в интернетах, а затем начинает генерировать отчёт, в котором английском по чёрному написано, на каком файле какие права стоят, а какие должны быть. Аккуратно выкусываем awk'ом строчки со словом permissions, который также извлекает 8 и 1 колонки (это права и имя файла соответственно), а утиля xargs доделывает всё остальное, вызывая chmod и подставляя ему результат работы предыдущего конвейера. В итоге эта команда проходится по всем файлам в каталоге /etc, возвращая им правильные права.

Стоит также отметить, что в /usr/local/etc/, которые я тоже сложил в SVN, права тоже поедут. Но там менее критично всё это, по крайней мере, не приведёт к потере управления удалённым сервером. А в идеале, надо делать отчёт с помощью утилиты mtree по правам доступа всех файлов, а после svn add / svn commit возвращать их с помощью этой же mtree обратно. Потому как после каждого commit'a права на закоммиченные файлы будут приводиться в соответствие с umask. Наверное, в идеале стоит написать какую-то обёртку для svn commit, которая будет затем возвращать права на файлы обратно. Либо пойти по пути asvn и реализовать хранение разрешений для файла в репозитории с помощью properties, а при коммите их перечитывать и делать chmod на файлы. А то западло получается...

Управление Hyper-V из-под обычного пользователя

13 января 2012, 17:12
Локальное управление

В блоге Алексея Кибкало вычитал полезную штуку — как позволить простому смертному пользователю, не обладающему администраторскими правами, управлять Hyper-V, создавать и запускать виртуальные машины и так далее.

Hyper-V может хранить настройки модели авторизации в Active Directory или в локальном файле в формате XML. По умолчанию после установки роли Hyper-V настройки хранятся в файле, который находится по адресу: %programdata%\Microsoft\Windows\Hyper-V\InitialStore.xml. Для того, чтобы изменить настройки, вам потребуется:
  • Запустить приложение MMC. (Для этого выберите пункт Run в Start Menu или нажмите комбинацию клавиш 'Windows Key + R', затем выполните mmc.exe).
  • В меню File выбрать Add/Remove Snap-in.
  • Добавить Authorization Manager.
  • В дереве консоли (левой панели ) выбрать Authorization Manager, затем в меню Action выбрать пункт Open Authorization Store.
  • Выбрать XML file в предлагаемом диалоге Select the authorization store type: и открыть файл по указанному выше пути. (Папка %programdata% является скрытой, так что проще будет скопировать путь целиком).
  • Выберите InitialStore.xml, затем Microsoft Hyper-V services, далее Role Assignments и в конце концов Administrator.
  • В меню Action выберите Assign Users and Groups, затем From Windows and Active Directory, далее выберите пользователя, которому хотите делегировать права на управления Hyper-V. Нажмите OK и закройте окно MMC. (При этом можно сохранить или отменить настройки MMC. Это не повлияет на изменения, внесенные вами в модель авторизации).
Удалённое управление
Как показала практика, это работает для случая, когда Hyper-V управляется локально. Для того, чтобы подключаться к нему удалённо, нужно проделать ещё ряд мероприятий:

Описанные в этой статье шаги применимы к версии RC0 гипервизора Hyper-V и RC0 версии клиентской утилиты управления Hyper-V Manager для Vista SP1 (x86 и x64).

Итак, на сервере следует выполнить следующие шаги:

Разрешить в Windows Firewall правило «Windows Management Instrumentation (WMI)» следующей командой:
netsh advfirewall firewall set rule group=«windows management instrumentation (wmi)» new enable=yes

Внимание: в различных локализованных ОС встроенные правила брандмауэера могут назваться по-разному. Необходимо указать название правила именно так, как оно выглядит в инструментах управления Windows Firewall. Например, в русской версии Windows Server 2008 приведенная выше строка будет выглядеть так:

netsh advfirewall firewall set rule group=«Инструментарий управления Windows (WMI — входящий трафик)» new enable=yes

Предоставить пользователю права на удаленный запуск (remote launch and activation) в DCOM. Это можно сделать как для конкретного пользователя или группы, так и для всех AUTHENTICATED USERS.
Нажмите Start, выберите Run, запустите dcomcnfg.exe.
В Component Services раскройте Computers, правой кнопкой нажмите на My Computer и выберите в меню пункт Properties.
В My Computer Properties раскройте COM Security.
В Launch and Activation Permissions выберите Edit Limits.
В случае, если пользователь не указан в списке Groups of user names в окне Launch Permission, добавьте его кнопкой Add.
В Launch Permission выберите пользователя или группу и в колонке Allow в Permissions for user укажите Remote Launch и Remote Activation. Нажмите OK.
Предоставить пользователю права на удаленное управление (remote enable) в пространстве имен (namespace) **root\CIMv2 и root\virtualization. Это можно сделать как для конкретного пользователя или группы, или для AUTHENTICATED USERS.
В Control Panel зайдите в Administrative Tools и запустите Computer Management.
В Computer Management раскройте Services and Applications, правой кнопкой выберите WMI Control и нажмите Properties.
В закладке Security выберите Advanced.
В случае, если пользователь не указан в списке Permission в окне Advanced Security Settings, добавьте его кнопкой Add.
В Advanced Security Settings выберите имя пользователя и нажмите Edit.
В выпадающем меню Apply To окна Permission Entry выберите This namespace and subnamespaces и укажите Remote Enable в колонке Allow. Нажмите OK.
Предоставьте пользователю права на Hyper-V. Эта процедура описана выше.
Перезагрузите сервер. (Если вы хотите избежать перезагрузки сервера, достаточно перезапустить следующие сервисы: winmgmt, vmms, vhdsvc & nvspwmi).
Внимание: если сервер с установленной ролью Hyper-V, которым вы хотите управлять удаленно, используя локальную запись с правами администратора, не входит в домен, и при этом на сервере включен User Account Control (UAC), то имейте в виду следующее. По умолчанию к локальным учетным записям при неинтерактивном (в том числе сетевом) доступе применяется UAC Filtering. То есть, даже если вы являетесь администратором сервера, при попытке удалённого подключения UAC предоставит вам права стандартного пользователя. Поэтому в таком случае вам потребуется напрямую предоставить пользователю права на Hyper-V способом, описанным в предыдущей статье.

Итак, сервер мы настроили. Теперь ряд настроек потребуется выполнить и на клиентском ПК с Vista SP1.

Разрешить на Windows Firewall правило «Windows Management Instrumentation (WMI)» командой
netsh advfirewall firewall set rule group=«windows management instrumentation (wmi)» new enable=yes

В русской версии Windows Vista эта же команда выглядит следующим образом:

netsh advfirewall firewall set rule group=«Инструментарий управления Windows (WMI — входящий трафик)» new enable=yes

А на системах с ОС, предшествующими Windows Vista (Windows XP / 2003), для этого служит команда

netsh firewall set service RemoteAdmin enable

На системах с ОС, предшествующих Vista (Windows XP / 2003), следует также добавить исключение для исполняемого файла Unsecapp.exe:
netsh firewall add allowedprogram program=%windir%\system32\wbem\unsecapp.exe name=UNSECAPP

Добавить в Windows Firewall исключение для исполняемого фалйла mmc.exe:
netsh firewall add allowedprogram program=%windir%\system32\mmc.exe name=«Microsoft Management Console»

Если клиент или сервер находятся в рабочей группе или они находятся в разных доменах, между которыми нет доверительных отношений, то соединение от сервера до клиента, устанавливаемое для доставки результирующей информации, происходит анонимно. Анонимное соединение завершается неудачно с кодом ошибки 0x80070005 или 0x8007000e до тех пор, пока анонимному соединнеию не будет дано право Remote Access на DCOM клиента. Дать это право можно, выполнив следующие шаги:
Нажмите Start, выберите Run, запустите dcomcnfg.exe.
В Component Services раскройте Computers, правой кнопкой выберите My Computer и укажите Properties.
В My Computer Properties раскройте COM Security.
В Launch and Activation Permissions выберите Edit Limits.
В окне Access Permissions выберите ANONYMOUS LOGON в списке Group or user names. В колонке Allow в Permissions for User укажите Remote Access и нажмите OK.
После выполнения всех описанных действий вы, наконец, получите возможность удаленно подключаться к серверу и управлять ролью Hyper-V.
Ctrl + ↓ Ранее