You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

28 KiB

Решения

0.

Например, diff <(ls foo) <(ls bar) покажет различия между файлами в каталогах foo и bar.

stud@stud12:~$ mkdir v1 v2
stud@stud12:~$ touch v1/a.txt v1/b.txt v2/b.txt v2/c.txt
stud@stud12:~$ diff <(ls v1) <(ls v2)
1d0
< a.txt
2a2
> c.txt

1.

Выполните следующие примеры:

stud@stud12:~$ false || echo "Opps, fail"
Opps, fail
stud@stud12:~$ true || echo "Opps, fail"
stud@stud12:~$ true && echo "Things went well"
Things went well
stud@stud12:~$ false && echo "Things went well"
stud@stud12:~$ true; echo "Always"
Always
stud@stud12:~$ false; echo "Always"
Always

*Что произойдёт в данном случае?

stud@stud12:~$ (echo red; echo green 1>&2) | echo blue

В большинстве случаев эта команда выводит "синий зеленый". Но примерно в 1% случаев она выводит "зеленый синий". А иногда выводится просто "синий". Для большей детерминированности добавим в пример задержки в 100мс. В реальности эта задержка на выполнение инструкций команды будет зависеть от загрузки системы и приоритетов выполнения задач.

stud@stud12:~$ (echo red; echo green 1>&2) | echo blue
blue
green
stud@stud12:~$ (echo red; echo green 1>&2) | (sleep 0.1; echo blue)
green
blue
stud@stud12:~$ (sleep 0.1; echo red; echo green 1>&2) | echo blue
blue

Мы имеем три порядка выполнения:

  • echo красного цвета происходит до выхода echo синего цвета, поэтому левая сторона не получает сигнал SIGPIPE, но после этого происходит echo зеленого цвета. На выходе получается "blue green".
  • Оболочка слева успевает выполнить оба своих echo до того, как выполнится echo blue. На выходе получается "green blue".
  • Выполняется echo blue и выходит, закрывая pipe, до того, как завершится echo red. Оболочка в левой части конвейера записывает вывод в закрытый pipe, получает SIGPIPE и выходит из неё, не продолжая выполнять echo green. На выходе получается "blue".

Команда echo red выполняется благодаря наличию буферизации. Так как ничто не читает её вывод STDOUT, то при заполнении буфера процесс echo red заблокируется. В случае отстуствия буфера echo red; echo green 1>&2 бы не выполнился.

Из man pipe: "since Linux 2.6.11, the pipe capacity has been increased to 16 pages, which translates to 65,536 bytes on systems with a page size of 4096 bytes."

(i=1; while true; do echo red; echo $i 1>&2; ((i+=1)); done) | sleep 1

Вывод остановится на значении 16384 = 65536 / 4, т.к. "red\n" равен 4 байта. Вызвав echo только с переносом строки вместо echo red мы получим значение 65536.

Таким образом, всё сводится к трём основным аспектам работы конвейров:

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

2

Напишите .sh скрипт в котором в переменную date присваивается значение текущей даты, в переменную claim - строка "snow is white". После вызова скрипт должен выводить строку, используя переменные date и claim:

Ср 14 сен 2022 14:50:46 +04: The claim that “snow is white” is true if and only if snow is white.

Сделайте файл исполняемым для владельца и вызовите указанием полного или относительного пути к нему.

содержимое скрипта 2.sh

#!/bin/bash
date=$(date)
claim='snow is white'
echo "$date: The claim that \"$claim\" is true if and only if $claim."
stud@stud12:~$ chmod u+x 2.sh
stud@stud12:~$ ./2.sh
Tue Oct 31 08:19:58 AM EDT 2023: The claim that "snow is white" is true if and only if snow is white.

3

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

stud@stud12:~$ cat 3.sh
#!/bin/bash

path_to_dir=$1
vars=('$0' '$1' '$2' '$#' '$?' '$$' '$_' '$@' '$RANDOM' '$HOME' '$USER')

mkdir $path_to_dir
cd $path_to_dir
for cmd in ${vars[@]}; do
  echo ${cmd}: $(eval echo $cmd) | tee -a all_vars;
done
stud@stud12:~$ ./3.sh test
$0: ./3.sh
$1: test
$2:
$#: 1
$?: 0
$$: 89872
$_: test
$@: test
$RANDOM: 29923
$HOME: /home/stud/
$USER: stud

4

Напишите аналогичный предыдущему заданию скрипт, но оберните все действия в функцию, а в конце скрипта выполните эту функцию. Проверьте работу скрипта.

stud@stud12:~$ cat 4.sh
#!/bin/bash

f () {
  path_to_dir=$1
  vars=('$0' '$1' '$2' '$#' '$?' '$$' '$_' '$@' '$RANDOM' '$HOME' '$USER')

  mkdir $path_to_dir
  cd $path_to_dir
  for cmd in ${vars[@]}; do
    echo ${cmd}: $(eval echo $cmd) >> all_vars;
  done
}

f $1

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

stud@stud12:~$ cat test2/all_vars
$0: ./4.sh
$1: test2
$2:
$#: 1
$?: 0
$$: 90118
$_: 90118
$@: test2
$RANDOM: 14079
$HOME: /home/stud/
$USER: stud

Доступна ли функция за пределами контекста скрипта?

stud@stud12:~$ f
-bash: f: command not found

Заведите внутри функции глобальную a и локальную переменную local b. Вызовите функцию в скрипте. Выведите обе переменные на экран внутри функции после присвоения значений переменным и после вызова функции.

stud@stud12:~$ cat 4_2.sh
#!/bin/bash
a='global a'
f () {
        local b='local b'
        echo a: ${a}, b: ${b}
}
echo a: ${a}, b: ${b}
f
echo a: ${a}, b: ${b}
stud@stud12:~$ ./4_2.sh
a: global a, b:
a: global a, b: local b
a: global a, b:

5.

Функции выполняются в текущей среде оболочке, тогда как скрипты выполняются в своем собственном процессе.

Проверьте, что функции могут изменять переменные среды, например, изменить текущий каталог, тогда как скрипт не может. Напишите скрипт, в котором инициализируются две переменные A и B. Перед одной из них указывается export. Выполните скрипт и посмотрите на выдачу команды env. Выполните файл командой source (документация в source --help и секции встроенных команд man bash).

$ cat 8.sh 
#!/bin/bash
A=1
export B=2

Какие переменные экспортировались в оболочку?

stud@stud12:~$ ./8.sh 
stud@stud12:~$ env | grep -E "^A|B"
stud@stud12:~$ source ./8.sh 
stud@stud12:~$ env | grep -E "^A|B"
B=2

Экспортировалась B.

6.

Попробуйте условные выражения в скрипте в if конструкции.

$ cat 6.sh 
if [[ true && true ]]
then
        echo "True"
fi

if [[ true || false ]]
then
        echo "True"
fi
stud@stud12:~$ ./6.sh 
True
True

6.5

7.

Напишите скрипт, который проходит по всем файлам, переданным в качестве аргументов, и ищет в них строку hello. Перенаправьте grep STDOUT и STDERR в специальный файл /dev/null. Для каждого файла в if создайте файл с содержимым hello, если grep завершился с ошибкой.

$ cat 7.sh 
#!/bin/bash
for f in $@; do
    grep hello $f 2> /dev/null 1> /dev/null
    if [[ $? -ne 0 ]]
    then
        echo $f has no hello in it. correcting.
        echo "hello" > $f
    fi
done
stud@stud12:/home/stud$ ./7.sh /tmp/one /tmp/two /tmp/three
/tmp/one has no hello in it. correcting.
/tmp/two has no hello in it. correcting.
/tmp/three has no hello in it. correcting.

stud@stud12:/home/stud$ ./7.sh /tmp/one /tmp/two /tmp/three

stud@stud12:/home/stud$ grep hello /tmp/* 2> /dev/null
/tmp/one:hello
/tmp/three:hello
/tmp/two:hello

8.

Установите пакет shellcheck и проверьте написанный вами ранее скрипт одноименной командой.

stud@stud12:/home/stud$ shellcheck 7.sh

In 7.sh line 2:
for f in $@; do
         ^-- SC2068 (error): Double quote array expansions to avoid re-splitting elements.


In 7.sh line 3:
    grep hello $f 2> /dev/null 1> /dev/null
               ^-- SC2086 (info): Double quote to prevent globbing and word splitting.

Did you mean:
    grep hello "$f" 2> /dev/null 1> /dev/null


In 7.sh line 4:
    if [[ $? -ne 0 ]]
          ^-- SC2181 (style): Check exit code directly with e.g. 'if ! mycmd;', not indirectly with $?.


In 7.sh line 6:
        echo $f has no hello in it. correcting.
             ^-- SC2086 (info): Double quote to prevent globbing and word splitting.

Did you mean:
        echo "$f" has no hello in it. correcting.


In 7.sh line 7:
        echo "hello" > $f
                       ^-- SC2086 (info): Double quote to prevent globbing and word splitting.

Did you mean:
        echo "hello" > "$f"

For more information:
  https://www.shellcheck.net/wiki/SC2068 -- Double quote array expansions to ...
  https://www.shellcheck.net/wiki/SC2086 -- Double quote to prevent globbing ...
  https://www.shellcheck.net/wiki/SC2181 -- Check exit code directly with e.g...

9.

9.1

Почему в этом примере не работает печать?

#!/usr/bin/env bash
set -e
i=0
let i++
echo "i is $i"
stud@stud12:/home/stud$ bash 9_1.sh

Согласно руководству, set -e завершает работу, если простая команда завершается с ненулевым статусом. Оболочка не завершает работу, если команда, которая завершилась неудачей, является частью списка команд, следующего сразу за ключевым словом while или until, частью проверки в операторе if, частью списка && или ||, или если возвращаемое значение команды инвертируется через !.

Команда let - это простая команда, и она не попадает ни под одно из исключений из приведенного выше списка. Более того, help let сообщает нам: "Если последний ARG вычисляется в 0, let возвращает 1; в противном случае возвращается 0". i++ равен 0, поэтому let i++ возвращает 1 и срабатывает set -e. Скрипт прерывается. Потому что мы добавили 1 в переменную.

9.2

Почему этот вариант иногда работает? В каких версиях bash он работает, а в каких - нет?

#!/usr/bin/env bash
set -e
i=0
((i++))
echo "i is $i"

((...)) не является простой командой в соответствии с грамматикой оболочки (man --pager='less -p ^SHELL GRAMMAR' bash). Поэтому она не может вызвать прерывание set -e, даже если в данном конкретном случае возвращает 1 (поскольку при установке i в 1 командой i++ кодом возврата считается 0, а 0 в математическом контексте считается ложью).

Однако в bash 4.1 это поведение изменилось. Упражнение 2 работает только в bash 4.0 и более ранних версиях! В bash 4.1 ((...)) квалифицируется как set -e abortion, и это упражнение ничего не выведет, как и упражнение 1.

Вы не можете рассчитывать на то, что он будет вести себя последовательно в разных выпусках оболочки.

9.3

Почему эти два сценария не идентичны?

#!/usr/bin/env bash
set -e
test -d nosuchdir && echo no dir
echo survived
#!/usr/bin/env bash
set -e
f() { test -d nosuchdir && echo no dir; }
f
echo survived
stud@stud12:/home/stud$ bash 9_3_a.sh
survived
stud@stud12:/home/stud$ bash 9_3_b.sh
stud@stud12:/home/stud$ 

В первом скрипте тестовая команда является частью любой команды, выполняемой в списке && или ||, кроме команды, следующей за последней && или || (man page Bash 4.2), поэтому она не приводит к выходу оболочки.

Во втором сценарии это также верно, поэтому оболочка не выходит сразу после команды test ... &&. Однако функция fn возвращает 1 (отказ), поскольку таков был статус выхода последней команды, выполненной в функции. Поэтому простая команда fn в основном теле скрипта возвращает 1 (отказ), что приводит к выходу оболочки.

9.4

Почему эти два сценария не идентичны?

#!/usr/bin/env bash
set -e
f() { test -d nosuchdir && echo no dir; }
f
echo survived
#!/usr/bin/env bash
set -e
f() { if test -d nosuchdir; then echo no dir; fi; }
f
echo survived
stud@stud12:/home/stud$ bash 9_4_a.sh
stud@stud12:/home/stud$ bash 9_4_b.sh
survived

Первый скрипт, приведенный выше, аналогичен второму скрипту из упражнения 3.

Во втором скрипте мы наблюдаем один из вариантов, в котором if и && не являются одним и тем же. В руководстве, в разделе Compound Commands, мы находим это предложение в определении if:

Статус выхода - это статус выхода последней выполненной команды или ноль, если ни одно условие не было выполнено. 

Поскольку термины "команда" и "условие" в man-странице особо не различаются, то, следуя первой части этого объяснения из man bash, может показаться, что статус выхода из структуры if должен быть статусом выхода тестовой команды, однако в данном случае тестовая команда является условием логической структуры if, поэтому применима вторая часть приведенного выше объяснения. Если test или любой список команд в условной позиции логической структуры возвращает код выхода, не равный 0, и при этом нет ветвей elif или else, то код выхода всей структуры if по-прежнему равен 0.

В более ясной формулировке, поскольку тест не является истинным и никакие последующие команды не выполняются, if должна возвращать 0. Это означает, что fn возвращает 0, и оболочка не завершается.

9.5

#!/usr/bin/env bash
set -e
read -r foo < configfile
stud@stud12:/home/stud$ bash 9_5.sh
9_5.sh: line 3: configfile: No such file or directory

Очевидно, что в случае отсутствия или нечитаемости файла configfile работа будет прервана. Он также прервется (возможно, неожиданно), если в файле отсутствует завершающая новая строка. Это происходит потому, что read возвращает код ошибки, если он достиг конца файла до прочтения ожидаемой новой строки. Однако содержимое файла все равно будет прочитано, и переменная будет заполнена.

Если бы не было set -e, скрипт правильно заполнил бы переменную и пошел дальше, и тот факт, что файл "неполный", не стал бы проблемой. Заметим, что использование списка while или if с read приведет к заполнению переменной в любом случае, поскольку оба варианта являются исключениями из правил set -e.

Рекомендация проста: не используйте set -e. Вместо этого добавьте собственную проверку ошибок.

10.

Напишите скрипт, который выполняет следующий код:

$ cat ./7.sh 
#!/usr/bin/env python
import sys
for arg in reversed(sys.argv[1:]):
        print(arg)
$ ./7.sh 1 2 3 4 5
5
4
3
2
1

11.

Скажем, у вас есть команда, которая редко дает сбой. Чтобы отладить её, вам нужно зафиксировать её выходные данные, но это может занять много времени, чтобы поймать неудачный запуск. Напишите сценарий bash, который запускает следующий сценарий до тех пор, пока он не завершится аварийно, записывает стандартный вывод и потоки ошибок в файлы и печатает все в конце. Бонусный балл, за то, что вы можете сообщить, сколько запусков потребовалось для сбоя сценария.

#!/usr/bin/env bash

n=$(( RANDOM % 100 ))

if [[ n -eq 42 ]]; then
  echo "Something went wrong"
  >&2 echo "The error was using magic numbers"
  exit 1
fi

echo "Everything went according to plan"
stud@stud12:~$ cat repeat_until_error.sh
#!/bin/bash
echo "" > output.log
echo "" > error.log
i=0
while true; do
  bash $1 2>> error.log 1>> output.log
  if [[ $? -ne 0 ]]; then
        echo Took "$i" iterations to get an error.
        echo "\noutput.log"
        cat output.log
        echo "\nerror.log"
        cat error.log
        break
  fi
  i=$((i+1))
done
stud@stud12:~$ ./repeat_until_error.sh ./random_bug_script.sh
Took 7 iterations to get an error.
\noutput.log

Everything went according to plan
Everything went according to plan
Everything went according to plan
Everything went according to plan
Everything went according to plan
Everything went according to plan
Everything went according to plan
Something went wrong
\nerror.log

The error was using magic numbers

12.

Настройка беспарольного перехода для суперпользователя между машинами.

root@stud12:~# cat ./.ssh/config
Host w001
        HostName 10.160.179.22

Host w002
        HostName 10.160.179.42

root@stud12:~# ip a show ens18 | grep inet
    inet 10.160.179.22/24 brd 10.160.179.255 scope global ens18
    inet6 fe80::5871:c6ff:fe12:f553/64 scope link

root@stud12:~# ssh w002 "ip a show ens18 | grep inet"
root@10.160.179.42's password:
    inet 10.160.179.42/24 brd 10.160.179.255 scope global ens18
    inet6 fe80::28d4:acff:fe73:5b9/64 scope link

root@stud12:~# ssh w002 "cat /etc/ssh/sshd_config | grep PermitRootLogin "
root@10.160.179.42's password:
# PermitRootLogin prohibit-password
PermitRootLogin yes
# the setting of "PermitRootLogin prohibit-password".

root@stud12:~# ssh-copy-id w002
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@10.160.179.42's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'w002'"
and check to make sure that only the key(s) you wanted were added.


root@stud12:~# ssh w002 "cat /etc/ssh/sshd_config | egrep '^PasswordAuthentication|^PermitRootLogin'"
PermitRootLogin prohibit-password
PasswordAuthentication no

Скрипт

root@stud12:~# cat create_user.sh 
#!/bin/bash

help() {
        echo "Usage: ./create_user.sh file_with_node_ip_addresses user_name"
}

if [[ $# < 2 ]]
then
        help
        exit
fi


nodes=$(cat $1)
user=$2
create_user_and_link_command="useradd $user --create-home --shell=/bin/bash --key HOME_MODE=0700; \
mkdir -p /ceph/$user -m 0700; \
chown $user:$user /ceph/$user; \
ln -s /ceph/$user /home/$user/ceph; \
chown $user:$user --no-dereference /home/$user/ceph"

mkdir -p /root/users.ssh/$user/
ssh-keygen -t ed25519 -N ""  -f "/root/users.ssh/$user/id_ed25519"
sed --in-place "s/root/user/" "/root/users.ssh/$user/id_ed25519.pub"
cat /root/users.ssh/$user/id_ed25519.pub > /root/users.ssh/$user/authorized_keys

echo "" > /root/users.ssh/$user/known_hosts
for node in $nodes; do
  ssh-keyscan -H "$node" >> /root/users.ssh/$user/known_hosts
done
 
for node in $nodes; do
  echo "$node" "$create_user_and_link_command"
  ssh "$node" "$create_user_and_link_command"
  scp -r /root/users.ssh/$user/ $node:/home/$user/.ssh
  ssh "$node" "chown -R $user:$user /home/$user/.ssh && chmod 0700 /home/$user/.ssh/"
done
root@stud12:~# cat nodes
10.160.179.22
10.160.179.42

Проверка

root@stud12:~# ./create_user.sh nodes user22
Generating public/private ed25519 key pair.
Your identification has been saved in /root/users.ssh/user22/id_ed25519
Your public key has been saved in /root/users.ssh/user22/id_ed25519.pub
The key fingerprint is:
SHA256:qz7py+1prq6iQ2YO2pmSoXBiujCVVKhnmqi6LvuA/gM root@stud12
The key's randomart image is:
+--[ED25519 256]--+
|   ..            |
|  ..             |
| ..              |
|..o.             |
|.=o     S        |
|XE.      .       |
|^*.o   ..        |
|%+=. .oo..       |
|X@ooo=OB=        |
+----[SHA256]-----+
# 10.160.179.22:22 SSH-2.0-OpenSSH_9.2p1 Debian-2
# 10.160.179.22:22 SSH-2.0-OpenSSH_9.2p1 Debian-2
# 10.160.179.22:22 SSH-2.0-OpenSSH_9.2p1 Debian-2
# 10.160.179.22:22 SSH-2.0-OpenSSH_9.2p1 Debian-2
# 10.160.179.22:22 SSH-2.0-OpenSSH_9.2p1 Debian-2
# 10.160.179.42:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u1
# 10.160.179.42:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u1
# 10.160.179.42:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u1
# 10.160.179.42:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u1
# 10.160.179.42:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u1
10.160.179.22 useradd user22 --create-home --shell=/bin/bash --key HOME_MODE=0700; mkdir -p /ceph/user22 -m 0700; chown user22:user22 /ceph/user22; ln -s /ceph/user22 /home/user22/ceph; chown user22:user22 --no-dereference /home/user22/ceph
useradd: user 'user22' already exists
known_hosts                                              100% 1957     6.2MB/s   00:00
id_ed25519.pub                                           100%   93   431.8KB/s   00:00
id_ed25519                                               100%  399     1.6MB/s   00:00
authorized_keys                                          100%   93   503.8KB/s   00:00
10.160.179.42 useradd user22 --create-home --shell=/bin/bash --key HOME_MODE=0700; mkdir -p /ceph/user22 -m 0700; chown user22:user22 /ceph/user22; ln -s /ceph/user22 /home/user22/ceph; chown user22:user22 --no-dereference /home/user22/ceph
useradd: user 'user22' already exists
known_hosts                                              100% 1957     4.9MB/s   00:00
id_ed25519.pub                                           100%   93   356.6KB/s   00:00
id_ed25519                                               100%  399     1.4MB/s   00:00
authorized_keys                                          100%   93   384.4KB/s   00:00
root@stud12:~# su - user22
user22@stud12:~$ ssh 10.160.179.42
The authenticity of host '10.160.179.42 (10.160.179.42)' can't be established.
ED25519 key fingerprint is SHA256:u9oSjb/BWxCt6BmjdCGf57l9Fp1wvSl8X6Ip3nHYCSU.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.160.179.42' (ED25519) to the list of known hosts.
Linux debian 6.1.0-13-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.55-1 (2023-09-29) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
user22@debian:~$ ssh 10.160.179.22
The authenticity of host '10.160.179.22 (10.160.179.22)' can't be established.
ED25519 key fingerprint is SHA256:rqN99l7uB8lGIeqGb4wEa/BIhlZBUPMF2pGLYQem9qI.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.160.179.22' (ED25519) to the list of known hosts.
Linux stud12 6.1.0-12-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.52-1 (2023-09-07) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
user22@stud12:~$
logout
Connection to 10.160.179.22 closed.
user22@debian:~$
logout
Connection to 10.160.179.42 closed.