Программы
Продолжаем дайвить в Bash

Продолжаем дайвить в Bash

awk, sed, примеры программ и разбор их

Оговорюсь заранее:
Каждая из заметок – моя шпаргалка на пару, чтобы не забыть, что по плану нужно рассказать (по сути – тезисы) + расшифровка, чтобы было понятно и неподготовленному читателю. Так что будьте аккуратны – с каждой заметкой вы становитесь чуть ближе к высшему образованию в областях "компьютерные науки" и "компьютерная безопасность".
Изображение Шпаргалка по командам Linux, FreeBSD и MacOS

Повторение

&, |, &&, ||, ;

Выполнение в фоне + задачи:

sleep 12345 &
jobs
fg

Пайпим (переводим вывод одной программы на вход другой):

cat * | less

Ленивые "и" и "или":

[[ 1 eq 1 ]] && echo equals || echo nope

Разделяем команды ";"

while read ln; do echo $ln; done;

cat, head, tail, grep, cut, tr, reverse

Однострочник. Шаг за шагом пишем его и понимаем, что же происходит:

ip addr show \
  | grep "inet" \
  | tr -s " " \
  | cut -d " " -f 3 \
  | cut -d "/" -f 1 \
  | sort -h \
  | reverse

xargs

Представляем вывод одной команды, как параметры другой:

ls -l | xargs file

regexp

Регулярные выражения используются как в утилитах, подобных grep, так и при сравнении в новом test-операторе:

grep '^root:' /etc/passwd

[[ $(pwd) =~ ^$HOME ]] && echo home sweat home

awk

awk читает за один раз одну строку, выполняет определенные действия в зависимости от заданных опций, и выводит результат. Одним из самых простых и популярных способов использования awk является выбор столбца из текстового файла или из вывода другой команды

dpkg -l | awk ' {print $2} ' > installed

Печатает только первый столбец, используя stdin:

cat /etc/hosts | grep ^[^#] | awk '{print $2}'

Печатает все столбцы, используя stdin:

awk '{print $0}'

Пример использования шаблона

cat /etc/hosts | grep ^[^#] | awk '/local/ {print $2}' | uniq

На самом деле, это язык программирования:

#! /bin/awk -f
# This is a program that prints \
"Hello, world!"
# and exits
BEGIN { print "Hello, world!" }

Пример поиска самой большой строки:

awk '{ if (length($0) > max) max = length($0) }
END { print max }'

Имена пользователей (разделитель ставим ":")

awk -F: '{ print $1 }' /etc/passwd | sort

sed

Как и awk — построчное изменение вывода.

cat report.txt | sed 's/Nick/John/g' > report_new.txt

Добавим 8 пробелов в начало:

sed 's/^/        /' file.txt > file_new.txt

Выводит все абзацы, начинающиеся с "Of course" и заканчивающиеся на "attention you pay".

sed -n '/Of course/,/attention you pay/p' myfile

Выводит только строки 12-18 файла file.txt

sed -n 12,18p file.txt

Если найден "boom", заменить aaa на bb:

sed '/boom/s/aaa/bb/' file.txt

Заменяет one на unos независимо от регистра, поэтому будет напечатано "unos TWO"

echo ONE TWO | sed "s/one/unos/I"

grep:

sed '/regexp/!d' file.txt

"Переписывание с доски"

Этот блок появился, потому что на предыдущей паре понял, что ребята не умеют программировать. Ну ничего – маленькими шажками, разбирая простые программы, придём к написанию собственных.

Балуемся с read

#!/bin/bash
echo -n "Введите значение: "
read var
echo "\"var\" = "$var""
echo
echo -n "Введите другое значение: "
read           #  Команда 'read' употребляется без указания переменной для ввода,
               #  тем не менее...
               #  По-умолчанию ввод осуществляется в переменную $REPLY.
var="$REPLY"
echo "\"var\" = "$var""
echo
exit 0

$IFS

echo "Список всех пользователей:"
OIFS=$IFS; IFS=:   # В файле /etc/passwd, в качестве разделителя полей
                   # используется символ ":" .
while read name passwd uid gid fullname ignore
do
    echo "$name ($fullname)"
done < /etc/passwd     # перенаправление ввода.
IFS=$OIFS              # Восстановление предыдущего состояния переменной $IFS.

Команда let производит арифметические операции над переменными. В большинстве случаев, ее можно считать упрощенным вариантом команды expr.

#!/bin/bash
let a=11          # То же, что и 'a=11'
let a=a+5         # Эквивалентно "a = a + 5"
                  # (Двойные кавычки и дополнительные пробелы делают код более удобочитаемым)
echo "11 + 5 = $a"
let "a <<= 3"     # Эквивалентно  let "a = a << 3"
echo "\"\$a\" (=16) после сдвига влево на 3 разряда = $a"
let "a /= 4"      # Эквивалентно let "a = a / 4"
echo "128 / 4 = $a"
let "a -= 5"      # Эквивалентно let "a = a - 5"
echo "32 - 5 = $a"
let "a = a * 10"  # Эквивалентно let "a = a * 10"
echo "27 * 10 = $a"
let "a %= 8"      # Эквивалентно let "a = a % 8"
echo "270 mod 8 = $a  (270 / 8 = 33, остаток = $a)"
exit 0

wc -l

exec < $1
let count=0
while read ln
do
      ((count++))
done
echo $count

Проверка параметра

if [[ $# -lt 1 ]]
then
  echo "Usage: $0 file ..."
  exit 1
fi

Пример работы с функциями

count_lines () {
  local f=$1  
  # this is the return value, i.e. non local
  l=`wc -l $f | sed 's/^\([0-9]*\).*$/\1/'`
}

if [ $# -lt 1 ]
then
  echo "Usage: $0 file ..."
  exit 1
fi

echo "$0 counts the lines of code"
l=0
n=0
s=0
while [ "$*" != ""  ]
do
        count_lines $1
        echo "$1: $l"
        n=$[ $n + 1 ]
        s=$[ $s + $l ]
	shift
done

echo "$n files in total, with $s lines in total"

Уточнение по ДЗ

  • "-" балл за отсутствие usage при "пустом вызове"
  • "-" балл за отсутствие -h и —help
  • "-" балл за ошибки http://www.shellcheck.net/ / "некрасивый" код
  • "+" балл за реализацию того же, но однострочником (отдельный код)

Нельзя пользоваться утилитами group, id и прочими, упрощающими получение данных из /etc/group /etc/passwd. Эти файлы – первоисточники информации, их и используйте!

Наш cut будет вырезать со строки $1 по стороку $2. Не забывайте, что может быть $2 < $1. + Рассказать, что делает оригинальный cut (за это не добавлю и не убавлю).