Shell tips

Материал из noname.com.ua
Перейти к навигацииПерейти к поиску

Короткие заметки по shell-программированию

еще пара примеров sed c комментариями

cat  access_log_users |  sed 's/^\[\(.*\):.*\].*JCORESESSIONID=aes\(.*\)==;.*POST.*/\1 \2/g'   

Несмотря на то что мануалов полно, замечу \(.*\) - это то что будет записано в переменную \1. Другими словами строка [ЧТО ТО 1:.*].*JCORESESSIONID=aesЧТО ТО 2==;.*POST.*
Будет заменена на строку ЧТО ТО 1 ЧТО ТО 2
Достаточно просто . Для строк, не попавших под регулярку никаких действий не будет.

Среднее, максимальное и минимальное значение столбца

В переменной $2 - номер столбца, обратить внимаени на "одинакрные в дыойных" кавычки - это нужно что бы передать параметры из шелла в awk

                                                                                                                                                                                                                                            
AVG=`cat $1 | awk ' { avg += $"'"$2"'" } END  { avg=avg/NR; print avg } '`                                                                                                                                                
echo AVG=$AVG                                                                                                                                                                                                                               
MIN=`cat $1 | awk 'BEGIN { min = 0} { if ( $"'"$2"'" < min ) min = $"'"$2"'" } END  {  print min } '`                                                                                                                                       
echo MIN=$MIN                                                                                                                                                                                                                               
MAX=`cat $1 | awk 'BEGIN { max = 0} { if ( $"'"$2"'" > max ) max = $"'"$2"'" } END  {  print max } '`                                                                                                                                       
echo MAX=$MAX                                                                                                                                                                                                                               

Передать переменные в SED и AWK

 $ echo "unix scripting"
unix scripting

In SED:

This is a general substitution. I am trying to replace "unix" with "BASH", so "unix scripting" will become "BASH scripting"

$ echo "unix scripting" | sed 's/unix/BASH/'
BASH scripting

Suppose, the text "BASH" is assigned to a variable called "var", now if I try to replace "unix" with "$var" in sed single quote notation, its not going to work as SED can't expand external variable in single quotes.

$ var="BASH"; echo "unix scripting" | sed 's/unix/$var/'
$var scripting

Try the same above with double quotes, this will work.

$ var="BASH"; echo "unix scripting" | sed "s/unix/$var/"
BASH scripting

In AWK

General substitution of "unix" with "BASH", will work. "unix scripting" will become "BASH scripting"

$ echo "unix scripting" | awk '{gsub(/unix/,"BASH")}; 1'
BASH scripting

"BASH" is assigned in variable "var". So the following substitution is not going to work.

$ var="BASH"; echo "unix scripting" | awk '{gsub(/unix/,"$var")}; 1'
$var scripting

Method1: See the "'" (double quote-single quote-double quote) before and after the variable var.

$ var="BASH"; echo "unix scripting" | awk '{gsub(/unix/,"'"$var"'")}; 1'
BASH scripting


Method2: Use awk -v flag this way. На солярисе работает только этот способ, как и на HP-UX. Для соляриса надо приментья нормальный awk - /usr/xpg4/bin/awk

$ var="BASH"; echo "unix scripting" | awk -v v="$var" '{sub(/unix/,v)}1'
BASH scripting

Округление

04:45:14-root@wiki:~$ printf "%0.f\n" 4.51
5


while БЕЗ subshell

Способ Ворона


exec 3<$TMP_FILE1                    
# 0 -stdin ... 2 stderr, 3 - first free descriptor
while  read -u 3  NUMBER TYPE
do
    echo NUMBER=$NUMBER TYPE=$TYPE      
done

Считываени нескольких переменных

Пример 1:

cat /tmp/service-perfdata | grep ttiweb  | grep APACHE_HTTPS | grep -v "CRITICAL" | awk '{ print $2" " $16}' | tail -5000 |  while  read DATE  TIMEOUT
do
	DATE1=`date -d @$DATE`
	TIMEOUT1=`echo $TIMEOUT |awk -F"[" '{ print $2 }'`
	echo $DATE1"|"$TIMEOUT1
done

Но для чтения инужно явно делать саб-шелл Пример 2:

for  FILE in  `ls -1 *.log`
do
        echo  $FILE | awk -F"-" '{ print $1" "$2" "$3" "$5 }' | ( read YYYY MM DD TIME
        # Subshell fore read!
                # echo $YYYY $MM $DD
                TIME=`echo $TIME | awk -F"__" '{print $1}'`
                hh=`echo $TIME | cut -b 1-2`
                mm=`echo $TIME | cut -b 3-4`
        D=`date +%s --date="${YYYY}/$MM/$DD $hh:$mm"`
        TMPFILE=`mktemp`
        cat $FILE | grep -v "USER" | head -10 > $TMPFILE
       )
done

if-els

коротко можно желать например, так

function export_data
{
	( 
	$JAVA_HOME/bin/java -Xbootclasspath/a:$CLASSPATH  -jar $BIARENGINE $CONFIG_FILE && ( 
	if test -f $BOXI_EXPORT_FILE
	then
		echo "${DATE_FOR_LOG_FILES}:BOXI Export=SUCCEEDED"  | tee -a $SYNC_STATUS_FILE
		clear_tmp
		exit 0
	else
		echo "${DATE_FOR_LOG_FILES}:BOXI Export=FAILED"  | tee -a $SYNC_STATUS_FILE
	fi  ) 
	) || ( echo "${DATE_FOR_LOG_FILES}:BOXI Export=FAILED"  | tee -a $SYNC_STATUS_FILE )

}


find

For example:

find . -mtime 0   # find files modified between now and 1 day ago
                  # (i.e., within the past 24 hours)
find . -mtime -1  # find files modified less than 1 day ago
                  # (i.e., within the past 24 hours, as before)
find . -mtime 1   # find files modified between 24 and 48 hours ago
find . -mtime +1  # find files modified more than 48 hours ago

find . -mmin +5 -mmin -10 # find files modified between
                          # 6 and 9 minutes ago

Using the "-printf" action instead of the default "-print" is useful to control the output format better than you can with ls or dir. You can use find with -printf to produce output that can easily be parsed by other utilities or imported into spreadsheets or databases. See the man page for the dozens of possibilities with the -printf action. (In fact find with -printf is more versatile than ls and is the preferred tool for forensic examiners even on Windows systems, to list file information.) For example the following displays non-hidden (no leading dot) files in the current directory only (no subdirectories), with an custom output format:

find . -maxdepth 1 -name '[!.]*' -printf 'Name: %16f Size: %6s\n'

"-maxdepth" is a Gnu extension. On a modern, POSIX version of find you could use this:

find . -path './*' -prune ...

On any version of find you can use this more complex (but portable) code:

find . ! -name . -prune ...

which says to "prune" (don't descend into) any directories except ".".

Note that "-maxdepth 1" will include "." unless you also specify "-mindepth 1". A portable way to include "." is:

 find . \( -name . -o -prune \) ...

[This information posted by Stephane Chazelas, on 3/10/09 in newsgroup comp.unix.shell.]

As a system administrator you can use find to locate suspicious files (e.g., world writable files, files with no valid owner and/or group, SetUID files, files with unusual permissions, sizes, names, or dates). Here's a final more complex example (which I saved as a shell script):

find / -noleaf -wholename '/proc' -prune \
     -o -wholename '/sys' -prune \
     -o -wholename '/dev' -prune \
     -o -wholename '/windows-C-Drive' -prune \
     -o -perm -2 ! -type l  ! -type s \
     ! \( -type d -perm -1000 \) -print

This says to seach the whole system, skipping the directories /proc, /sys, /dev, and /windows-C-Drive (presumably a Windows partition on a dual-booted computer). The Gnu -noleaf option tells find not to assume all remaining mounted filesystems are Unix file systems (you might have a mounted CD for instance). The "-o" is the Boolean OR operator, and "!" is the Boolean NOT operator (applies to the following criteria).

So these criteria say to locate files that are world writable ("-perm -2", same as "-o=w") and NOT symlinks ("! -type l") and NOT sockets ("! -type s") and NOT directories with the sticky (or text) bit set ("! \( -type d -perm -1000 \)"). (Symlinks, sockets and directories with the sticky bit set are often world-writable and generally not suspicious.)

A common request is a way to find all the hard links to some file. Using "ls -li file" will tell you how many hard links the file has, and the inode number. You can locate all pathnames to this file with:

  find mount-point -xdev -inum inode-number

Since hard links are restricted to a single filesystem, you need to search that whole filesystem so you start the search at the filesystem's mount point. (This is likely to be either "/home" or "/" for files in your home directory.) The "-xdev" options tells find to not search any other filesystems.

(While most Unix and all Linux systems have a find command that supports the "-inum" criterion, this isn't POSIX standard. Older Unix systems provided the "ncheck" utility instead that could be used for this.)


Solaris-specific

У find на солярисе нет ключа -mmin иногда можно обойтись ключем

find $LOG_DIR/j2ee/FAM/ -newer /tmp/1/logs-fam-indusora-Fri-Aug-20.tar.bz2

Копирование по сети

Принимающяя:

nc -l 1234 | dd of=/tmp/image.img bs=4096

Отдающая:

dd if=/dev/sda bs=4096 | nc 1.2.3.4 1234


Примеры использования Awk

с опеннета

Использование сокращений.

Конструкцию, используемую для вывода строк соответствующих заданной маске:

   awk '{if ($0 ~ /pattern/) print $0}'

можно сократить до

   awk '/pattern/'

Условие в awk может быть задано вне скобок, т.е. получаем:

   awk '$0 ~ /pattern/ {print $0}'

По умолчанию, действия производятся со всей строкой, $0 можно не указывать:

   awk '/pattern/ {print}'

print - является действием по умолчанию, его тоже можно не указывать.

   awk '/pattern/'

Для вывода значения первого столбца строки, в которой присутствует маска LEGO:

   awk '/LEGO/ {print $1}'

Для вывода значения первого столбца строки, во втором столбце которой присутствует маска LEGO:

 
  awk '$2 ~ /LEGO/ {print $1}'

Для замены слова LIGO на LEGO и вывода только измененных строк можно использовать:

   awk '{if(sub(/LIGO/,"LEGO")){print}}'

Но есть нужно выводить все строки (как sed 's/LIGO/LEGO/'), конструкцию можно упростить

(1 - true для всех строк):
   awk '{sub(/LIGO/,"LEGO")}1'

Вывести все строки, за исключением каждой шестой:

   awk 'NR % 6'

Вывести строки, начиная с 6 (как tail -n +6 или sed '1,5d'):

   awk 'NR > 5'

Вывести строки, в которых значение второго столбца равно foo:

   awk '$2 == "foo"'

Вывести строки, в которых 6 и более столбцов:

   awk 'NF >= 6'

Вывести строки, в которых есть слова foo и bar:

   awk '/foo/ && /bar/'

Вывести строки, в которых есть слово foo, но нет bar:

   awk '/foo/ && !/bar/'

Вывести строки, в которых есть слова foo или bar (как grep -e 'foo' -e 'bar'):

   awk '/foo/ || /bar/'

Вывести все непустые строки:

   awk 'NF'

Вывести все строки, удалив содержимое последнего столбца:

   awk 'NF--'

Вывести номера строк перед содержимым:

   awk '$0 = NR" "$0'

Заменим команды (пропускаем 1 строку, фильтруем строки с foo и заменяем foo на bar, затем переводим в верхний регистр и выводим значение второго столбца)

   cat test.txt | head -n +1 | grep foo | sed 's/foo/bar/' | tr '[a-z]' '[A-Z]' | cut -d ' ' -f 2

аналогичной конструкцией на awk:

 
  cat test.txt | awk 'NR>1 && /foo/{sub(/foo/,"bar"); print toupper($2)}'

Использование диапазонов.

Вывести группу строк, начиная со строки, в которой есть foo, и заканчивая строкой, в которой есть bar:

   awk '/foo/,/bar/'

Исключив из вывода строки с вхождением заданных масок:

 
  awk '/foo/,/bar/{if (!/foo/ && !/bar/)print}'

Более оптимальный вариант:

   awk '/bar/{p=0};p;/foo/{p=1}'

Исключить только строку с завершающим вхождением (bar)

 
  awk '/bar/{p=0} /foo/{p=1} p'

Исключить только строку с начальным вхождением (foo)

 
  awk 'p; /bar/{p=0} /foo/{p=1}'

Разбиение файла по шаблонам.

Имеется файл (file), в котором группы строк разделены шаблонами FOO1,FOO2 и т.д. Необходимо записать данные, находящиеся между метками FOO в разные файлы, соответствующие указанным в FOO номерам.

   awk -v n=1 '/^FOO[0-9]*/{close("out"n);n++;next} {print > "out"n}' file

В GNU Awk можно сделать так:

   LC_ALL=C gawk -v RS='FOO[0-9]*\n' -v ORS= '{print > "out"NR}' file

Парсинг CSV.

По умолчанию в качестве разделителя используются пробел и табуляция. Чтобы определить иной разделитель, например запятую, нужно использовать FS=',' или опцию "-F". В качестве параметра может быть задано регулярное выражение, например, FS='^ *| *, *| *$' Но для разбора CSV это не подойдет, так как пробелы могут присутствовать и внутри трок, поэтому проще вырезать лидирующие пробелы перед и после запятой:

   FS=','
   for(i=1;i<=NF;i++){
     gsub(/^ *| *$/,"",$i);
     print "Field " i " is " $i;
   }

Если в CSV данные помещены в кавычки, например "field1","field2", то подойдет такой скрипт:

   FS=','
   for(i=1;i<=NF;i++){
     gsub(/^ *"|" *$/,"",$i);
     print "Field " i " is " $i;
   }

Но скрипт придется усовершенствовать для разбора полей вида:

field1, "field2,with,commas"  ,  field3  ,  "field4,foo"

   $0=$0",";  
   while($0) {
     match($0,/[^,]*,| *"[^"]*" *,/);
     sf=f=substr($0,RSTART,RLENGTH); 
     gsub(/^ *"?|"? *,$/,"",f);
     print "Field " ++c " is " f;
     sub(sf,"");
   }

Проверка IPv4 адреса.

   awk -F '[.]' 'function ok(n) {
     return (n ~ /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$/)
   }
   {exit (ok($1) && ok($2) && ok($3) && ok($4))}'

Сравнение двух файлов.

Вывод всех дублирующихся строк из двух неотсортированных файлах file1 и file2:

   awk '!($0 in a) {c++;a[$0]} END {exit(c==NR/2?0:1)}' file1 file2

Вывод только выделенных блоков текста.

Например, чтобы показать из файла с текстом только текст, отмеченный как =текст= можно использовать:

   awk -v RS='=' '!(NR%2)'

с форматированием переносов строк:

   awk -v RS='=' '!(NR%2){gsub(/\n/," ");print}'

Ссылки