Объединение команд


Основы Linux

В модуле "Обработка данных" Вы познакомились с концепцией использования нескольких команд, между которыми данные передаются через каналы, для решения задач, которые отдельные команды не могут выполнить в одиночку. Разумеется, эта концепция применима и независимо от передачи данных: иногда Вам может потребоваться последовательно выполнить несколько команд ради накопительного эффекта.

В этом модуле рассматриваются различные способы объединения команд, помимо пайпов. По его завершении Вы будете готовы к написанию полноценных shell-скриптов!

Самый простой способ объединить команды — использовать символ ;. В большинстве контекстов ; разделяет команды примерно так же, как клавиша Enter разделяет строки. Таким образом, следующие две команды:

hacker@chain:~$ echo SCHOOL > CTF
hacker@chain:~$ cat CTF
SCHOOL
hacker@chain:~$

Эквивалентны этой строке:

hacker@chain:~$ echo SCHOOL > CTF; cat CTF
SCHOOL
hacker@chain:~$

Когда Вы нажимаете Enter, оболочка выполняет введённую команду и после её завершения выводит приглашение для ввода следующей. Точка с запятой работает аналогично, только без промежуточного приглашения — Вы вводите обе команды сразу, до их выполнения.


Попробуйте данный подход на практике. На этом задании нужно запустить /challenge/ctf, а затем /challenge/school, объединив их точкой с запятой.

Connect with SSH

Link your SSH key, then connect with: ssh hacker@dojo.pwn.college

В модуле «Процессы в Linux» Вы познакомились с кодами завершения. Теперь давайте используем их для объединения команд.

Оператор && позволяет выполнить вторую команду только в том случае, если первая завершилась успешно (по соглашению Linux, это означает код завершения 0). Он называется оператором «И», поскольку оба условия должны быть выполнены: первая команда должна завершиться успешно, И только тогда выполнится вторая. Это крайне полезно в сложных командных рабочих процессах, где одни действия зависят от успешного выполнения других.

Синтаксис:

hacker@chain:~$ команда1 && команда2

Это означает: «Выполни команда1, и ЕСЛИ она завершится успешно, выполни команда2». Примеры:

hacker@chain:~$ touch /home/hacker/file && echo "это выполнится"
success
это выполнится
hacker@chain:~$ touch /file && echo "это НЕ выполнится"
touch: cannot touch '/file': Permission denied
hacker@chain:~$

Второй вызов touch завершился ошибкой, поскольку у пользователя hacker нет прав на запись в /file, поэтому echo не выполнился.


В этом задании нужно объединить программы /challenge/first-success и /challenge/second с помощью оператора &&. Попробуйте сначала запустить каждую команду отдельно, чтобы увидеть, что произойдёт (флаг при этом получить не удастся). Но если объединить их через &&, флаг появится!

Connect with SSH

Link your SSH key, then connect with: ssh hacker@dojo.pwn.college

Вы только что познакомились с оператором &&, который выполняет вторую команду только при успешном завершении первой. Теперь изучим его противоположность: оператор || выполняет вторую команду только в том случае, если первая завершилась с ошибкой (код завершения не равен нулю). Он называется оператором «ИЛИ», поскольку: либо первая команда завершится успешно, ИЛИ выполнится вторая.

Синтаксис:

hacker@chain:~$ команда1 || команда2

Это означает: «Выполни команда1, и ЕСЛИ она завершится с ошибкой, выполни команда2».

Например:

hacker@chain:~$ touch /file || echo "touch завершился с ошибкой, это выполнится"
touch: cannot touch '/file': Permission denied
touch завершился с ошибкой, это выполнится
hacker@chain:~$ touch /home/hacker/file || echo "это НЕ выполнится"
hacker@chain:~$

Оператор || чрезвычайно полезен для организации резервных команд и обработки ошибок.


В этом задании Вам нужно объединить /challenge/first-failure и /challenge/second с помощью оператора ||. Вперёд!

Connect with SSH

Link your SSH key, then connect with: ssh hacker@dojo.pwn.college

По мере объединения большого числа команд (для достижения сложных эффектов) работа в приглашении быстро становится неудобной. В таких случаях команды можно поместить команды в файл, называемый shell-скриптом, и выполнить его! Рассмотрим уже знакомый нам способ с точкой с запятой:

hacker@chain:~$ echo SCHOOL > CTF; cat CTF
SCHOOL
hacker@chain:~$

Создадим shell-скрипт ctf.sh (по соглашению shell-скрипты часто именуются с суффиксом .sh):

echo SCHOOL > CTF
cat CTF

Затем выполним его, передав в качестве аргумента новому экземпляру оболочки (bash)! При таком вызове оболочка читает команды из файла, а не от пользователя.

hacker@chain:~$ ls
hacker@chain:~$ bash ctf.sh
SCHOOL
hacker@chain:~$ ls
CTF
hacker@chain:~$

Как видите, shell-скрипт выполнил обе команды: создал и вывел файл CTF.


Теперь Ваша очередь! Как и на предыдущем уровне, запустите /challenge/ctf, а затем /challenge/school, но на этот раз оформите их в виде shell-скрипта с именем x.sh и выполните его с помощью bash!

ПРИМЕЧАНИЕ: Ранее мы ещё не рассматривали редакторы файлов командной строки в Linux. Мы обязательно рассмотрим редакторы в продвинутом курсе, а пока Вы можете воспользоваться перенаправлением из cat:

hacker@chain:~$ cat > x.sh
Напишите скрипт.
Нажмите Ctrl+D когда закончите.
hacker@chain:~$

Connect with SSH

Link your SSH key, then connect with: ssh hacker@dojo.pwn.college

Попробуем кое-что посложнее!

Вы уже передавали вывод между программами через |, но до сих пор это происходило между выводом одной команды и вводом другой. Но что если нужно отправить вывод нескольких программ в одну команду? Существует несколько способов сделать это... здесь мы рассмотрим простейший: перенаправление вывода из скрипта.

С точки зрения оболочки Ваш скрипт — такая же команда, как любая другая. Это означает, что его ввод и вывод можно перенаправлять так же, как Вы делали это в модуле «Управление потоками». Например, вывод можно записать в файл:

hacker@chain:~$ cat script.sh
echo CTF
echo SCHOOL
hacker@chain:~$ bash script.sh > output
hacker@chain:~$ cat output
CTF
SCHOOL
hacker@chain:~$

Работают все методы перенаправления: > для stdout, 2> для stderr, < для stdin, >> и 2>> для дозаписи, >& для перенаправления в другой файловый дескриптор и | для передачи в другую команду.


На этом уровне мы будем практиковать передачу вывода (|) из скрипта в другую программу. Как и раньше, Вам нужно создать скрипт, который вызывает команду /challenge/ctf, а затем команду /challenge/school, и передать вывод скрипта через пайп в единственный вызов команды /challenge/solve!

Connect with SSH

Link your SSH key, then connect with: ssh hacker@dojo.pwn.college

Когда Вы написали свой первый shell-скрипт, вы вызывали его через bash script.sh. Зачем нужен этот bash? Давайте подробно разберемся.

Когда Вы вводите bash script.sh, Вы запускаете команду bash с аргументом script.sh. Это указывает bash читать команды из файла script.sh вместо стандартного ввода — так и выполняется Ваш скрипт.

Оказывается, явного вызова bash можно избежать. Если файл скрипта является исполняемым (вспомните раздел «Права доступа в Linux»), его можно запустить напрямую по относительному или абсолютному пути! Например, если создать script.sh в домашнем каталоге и сделать его исполняемым, его можно запустить как /home/hacker/script.sh, ~/script.sh или (если текущий каталог — /home/hacker) ./script.sh.


Попробуйте прямо сейчас!

Создайте shell-скрипт в домашнем каталоге пользователя, который вызывает /challenge/solve, сделайте его исполняемым и запустите без явного указания bash!

Connect with SSH

Link your SSH key, then connect with: ssh hacker@dojo.pwn.college

Вы уверенно движетесь к новой жизни автора shell-скриптов! Однако до сих пор Ваши скрипты запускались только из оболочки. На предыдущем уровне всё работало отлично (поскольку Вы запускали скрипт из оболочки bash), но это не сработает, если скрипт вызывается, например, программой на Python (или на любом другом языке).

На момент запуска программы ядро Linux сначала изучает файл, чтобы определить, как его следует выполнить. При этом расширение файла НЕ используется (именно поэтому у shell-скриптов не обязательно должно быть расширение .sh, у Python-скриптов — .py и т. д.). Вместо этого Linux смотрит на первые несколько байт файла.

Существует множество типов программ, но если файл начинается с символов #! (часто называемых «шебангом»), Linux считает файл интерпретируемой программой, а остаток строки — путём к интерпретатору. Ядро запускает интерпретатор, передавая путь к файлу программы единственным аргументом.

Рассмотрим shell-скрипт script.sh:

#!/bin/bash

echo "Привет, хакеры!"

Его можно выполнить так:

hacker@chain:~$ chmod a+x script.sh
hacker@chain:~$ ./script.sh
Привет, хакеры!
hacker@chain:~$

При выполнении ./script.sh Linux открыл файл, прочитал первую строку, извлёк из неё /bin/bash в качестве интерпретатора и выполнил /bin/bash ./script.sh для запуска скрипта!

Обратите внимание: строка шебанга должна быть САМОЙ ПЕРВОЙ строкой файла — перед ней не должно быть ни пустых строк, ни пробелов!

Часто встречающиеся шебанги:

  • #!/bin/bash — для bash-скриптов
  • #!/usr/bin/python3 — для Python-скриптов
  • #!/bin/sh — для скриптов POSIX shell --- это более примитивный предшественник bash с меньшим числом функций, но большей совместимостью с не-Linux системами!

В этом задании создайте скрипт по пути /home/hacker/solve.sh с шебангом для bash-скриптов, который выводит строку «hack the planet». Не забудьте сделать его исполняемым, а затем запустите /challenge/run, чтобы проверить работу скрипта!

Connect with SSH

Link your SSH key, then connect with: ssh hacker@dojo.pwn.college

Вы уже умеете создавать shell-скрипты, но пока что они представляли собой просто список команд. Скрипты становятся значительно мощнее, когда умеют принимать аргументы!

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

hacker@chain:~$ bash myscript.sh hello world

Скрипт может обращаться к этим аргументам с помощью специальных переменных:

  • $1 содержит первый аргумент («hello»)
  • $2 содержит второй аргумент («world»)
  • $3 содержал бы третий аргумент (если бы он был)
  • ...и так далее

Простой пример:

hacker@chain:~$ cat myscript.sh
#!/bin/bash
echo "Первый аргумент: $1"
echo "Второй аргумент: $2"
hacker@chain:~$ bash myscript.sh ctf school
Первый аргумент: ctf
Второй аргумент: school
hacker@chain:~$

В этом задании Вам нужно написать скрипт по пути /home/hacker/solve.sh, который:

  1. Принимает два аргумента
  2. Выводит их в ОБРАТНОМ порядке (сначала второй аргумент, затем первый)

Например:

hacker@chain:~$ bash /home/hacker/solve.sh ctf school
school ctf
hacker@chain:~$

Когда скрипт будет работать правильно, запустите /challenge/run, чтобы получить флаг!

Connect with SSH

Link your SSH key, then connect with: ssh hacker@dojo.pwn.college

Теперь, когда Вы умеете использовать аргументы в скриптах, сделаем их умнее с помощью условной логики!

В bash для принятия решений используются операторы if:

if [ "$1" == "ping" ]
then
    echo "pong"
fi

В переводе на русский язык: «если первый аргумент равен "ping", вывести "pong"».

Синтаксис несколько требователен по ряду причин:

  • Во-первых, после if (в отличие, например, от языка C), после [ и перед ] обязательно должны стоять пробелы.
  • Во-вторых, if, then и fi должны находиться на отдельных строках (или разделяться точками с запятой) — их нельзя записать в одном операторе.
  • Также немного необычно: вместо endif, end или чего-то подобного, блок if завершается словом fi (if наоборот).

Это нужно просто запомнить.

ПРИМЕЧАНИЕ: Если хотите узнать, что ещё можно проверять в условии, помимо равенства строк, прочитайте подробнее [test()] (https://www.opennet.ru/man.shtml?topic=test).


В этом задании напишите скрипт по пути /home/hacker/solve.sh, который:

  • Принимает один аргумент
  • Если аргумент равен «ctf», выводит «school»
  • При любом другом вводе ничего не выводит

Пример:

hacker@chain:~$ bash /home/hacker/solve.sh ctf
school
hacker@chain:~$ bash /home/hacker/solve.sh foo
hacker@chain:~$

Когда скрипт будет работать правильно, запустите /challenge/run, чтобы получить флаг!

Connect with SSH

Link your SSH key, then connect with: ssh hacker@dojo.pwn.college

Ваши операторы if до сих пор обрабатывали конкретные случаи, но что насчёт всего остального? Для этого существует else!

Ветвь else выполняется, когда условие if ложно:

if [ "$1" == "Привет" ]
then
    echo "Привет!"
else
    echo "Не понимаю"
fi

Обратите внимание:

  • У else нет условия — он перехватывает всё, что не совпало с предыдущими проверками.
  • У else также нет оператора then.
  • Наконец, fi ставится после блока else, обозначая конец всей составной конструкции!

Ветвь else необязательна, она нужна лишь тогда, когда этого требует логика задачи.

Практический пример:

if [ "$1" == "start" ]
then
    echo "Запуск сервиса..."
else
    echo "Неизвестная команда. Для запуска используйте 'start'."
fi

В этом задании напишите скрипт по пути /home/hacker/solve.sh, который:

  • Принимает один аргумент
  • Если аргумент равен «ctf», выводит «school»
  • При любом другом вводе выводит «nope»

Пример:

hacker@chain:~$ bash /home/hacker/solve.sh ctf
school
hacker@chain:~$ bash /home/hacker/solve.sh hack
nope
hacker@chain:~$ bash /home/hacker/solve.sh anything
nope
hacker@chain:~$

Когда скрипт будет работать правильно, запустите /challenge/run, чтобы получить флаг!

Connect with SSH

Link your SSH key, then connect with: ssh hacker@dojo.pwn.college

Вы научились использовать одиночный оператор if для проверки условия. Но что если нужно проверить несколько условий? Для этого можно использовать elif (сокращение от «else if» — «иначе если»):

if [ "$1" == "один" ]
then
    echo "1"
elif [ "$1" == "два" ]
then
    echo "2"
elif [ "$1" == "три" ]
then
    echo "3"
else
    echo "неизвестно"
fi

Обратите внимание: после elif так же нужен then, как и после if. Ветвь else в конце по-прежнему перехватывает всё, что не совпало с предыдущими условиями.

ПРИМЕЧАНИЕ: При написании скрипта внимательно следите за пробелами в примерах. В отличие от многих других языков, bash требует, чтобы [ и ] были отделены от других символов пробелами — иначе условие не будет разобрано корректно.


В этом задании напишите скрипт по пути /home/hacker/solve.sh, который:

  • Принимает один аргумент
  • Если аргумент равен «gotta», выводит «catch them all»
  • Если аргумент равен «ctf», выводит «school»
  • Если аргумент равен «learn», выводит «linux»
  • При любом другом вводе выводит «unknown»

Пример:

hacker@chain:~$ bash /home/hacker/solve.sh gotta
catch them all
hacker@chain:~$ bash /home/hacker/solve.sh ctf
school
hacker@chain:~$ bash /home/hacker/solve.sh learn
linux
hacker@chain:~$ bash /home/hacker/solve.sh foo
unknown
hacker@chain:~$

Когда скрипт будет работать правильно, запустите /challenge/run, чтобы получить флаг!

Connect with SSH

Link your SSH key, then connect with: ssh hacker@dojo.pwn.college

Shell-скрипты пишете не только Вы! Они очень удобны для выполнения простых задач на уровне системы и широко используются разработчиками и системными администраторами. На самом деле, удивительно большая доля программ на типичной Linux-машине — это shell-скрипты.

Не стесняйтесь читать код других людей! Чтение кода — важнейшая стратегия при освоении новых навыков: Вы можете видеть, как реализована та или иная функциональность, и использовать эти подходы в собственных скриптах. Однако будьте внимательны: некоторые файлы программ содержат машинный код и будут нечитаемы для человека.


На этом уровне мы будем учиться читать shell-скрипты. /challenge/run — это shell-скрипт, который требует ввода секретного пароля, однако этот пароль зашит прямо в сам скрипт! Прочитайте скрипт (например, с помощью cat), найдите пароль и получите флаг!

Connect with SSH

Link your SSH key, then connect with: ssh hacker@dojo.pwn.college