Среда командой строки

В данной лекции мы рассмотрим несколько способов, с помощью которых вы сможете оптимизировать ваш рабочий процесс при использовании командной строки. Мы уже некоторое время работаем с командной строкой, но в основном мы были сфокусированны на исполнении различных команд. Сейчас мы посмотрим на то, как одновременно запускать несколько различных процессов, ведя их учет, как полностью останавливать или приостанавливать определенный процесс и как запускать процесс в фоновом режиме.

Мы также узнаем о различных способах улучшения вашей командной строки и других инструментов путем определения псевдонимов и их настройки с использованием dotfiles. Оба этих метода могут помочь вам сэкономить время, например, используя те же настройки на всех ваших машинах без необходимости вводить длинные команды. Мы рассмотрим, как работать с удаленными машинами с помощью SSH.

Контроль задач

В некоторых случаях вам потребуется прервать задание во время его выполнения, например, если команда слишком долго выполняется (например, find с очень большой структурой директорий для поиска). В большинстве случаев вы можете выполнить Ctrl-C, и команда остановится. Но как это на самом деле работает и почему иногда это не удается остановить процесс?

Уничтожение процесса

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

В нашем случае, когда мы нажимаем Ctrl-C, это подает команду оболочке отправить процессу сигнал SIGINT.

Вот простой пример программы на Python, которая перехватывает SIGINT и игнорирует его, больше не останавливаясь. Чтобы завершить эту программу, теперь нужно использовать сигнал SIGQUIT, нажав Ctrl-\.

#!/usr/bin/env python
import signal, time

def handler(signum, time):
    print("\nI got a SIGINT, but I am not stopping")

signal.signal(signal.SIGINT, handler)
i = 0
while True:
    time.sleep(.1)
    print("\r{}".format(i), end="")
    i += 1

Вот что произойдет, если мы дважды отправим сигнал SIGINT этой программе, а затем отправим SIGQUIT. Обратите внимание, что ^ - то, как отображается Ctrl при введении в терминале.

$ python sigint.py
24^C
I got a SIGINT, but I am not stopping
26^C
I got a SIGINT, but I am not stopping
30^\[1]    39913 quit       python sigint.py

Хотя SIGINT и SIGQUIT обычно связаны с запросами, относящимися к терминалу, более общим сигналом, чтобы попросить процесс корректно завершиться, является сигнал SIGTERM. Для отправки этого сигнала мы можем использовать команду kill с синтаксисом kill -TERM <PID>.

Приостановка и перевод процесса в фоновый режим

Сигналы могут делать и другие вещи, помимо завершения процесса. Например, SIGSTOP приостанавливает процесс. Нажатие Ctrl-Z в терминале подаст команду оболочке отправить сигнал SIGTSTP, что означает Terminal Stop (то есть версия SIGSTOP для терминала).

Затем мы можем продолжить выполнение задачи в переднем плане или в фоновом режиме, используя fg или bg, соответственно.

Команда jobs приводит список незавершенных задач, ассоциированных с текущей сессией терминала. Вы можете обращаться к этим задачам, используя их pid (вы можете использовать команду pgrep, чтобы узнать его). Более интуитивно, вы также можете ссылаться на процесс, используя символ процента, за которым следует его номер задачи (отображаемый с помощью jobs). Чтобы ссылаться на последнюю задачу, отправленную в фон, вы можете использовать специальный параметр $!.

Еще одна вещь, которую стоит знать, это то, что суффикс & в команде позволит выполнить команду в фоновом режиме, вернув вам контроль командной строки, хотя эта задача все еще будет использовать STDOUT оболочки, что может быть раздражающим факторов (в таких случаях используйте перенаправления вывода).

Чтобы перевести уже запущенную программу в фоновый режим, можно использовать Ctrl-Z, а затем bg. Обратите внимание, что процессы в фоновом режиме все еще являются дочерними процессами вашего терминала и завершатся, если вы закроете терминал (это отправит еще один сигнал, SIGHUP). Чтобы этого не произошло, вы можете запустить программу с nohup (оболочкой для игнорирования SIGHUP) или использовать disown, если процесс уже был запущен. В качестве альтернативы, вы можете использовать мультиплексор терминалов, как мы увидим в следующем разделе.

Ниже приведена примерная сессия, чтобы продемонстрировать некоторые из этих концепций.

$ sleep 1000
^Z
[1]  + 18653 suspended  sleep 1000

$ nohup sleep 2000 &
[2] 18745
appending output to nohup.out

$ jobs
[1]  + suspended  sleep 1000
[2]  - running    nohup sleep 2000

$ bg %1
[1]  - 18653 continued  sleep 1000

$ jobs
[1]  - running    sleep 1000
[2]  + running    nohup sleep 2000

$ kill -STOP %1
[1]  + 18653 suspended (signal)  sleep 1000

$ jobs
[1]  + suspended (signal)  sleep 1000
[2]  - running    nohup sleep 2000

$ kill -SIGHUP %1
[1]  + 18653 hangup     sleep 1000

$ jobs
[2]  + running    nohup sleep 2000

$ kill -SIGHUP %2

$ jobs
[2]  + running    nohup sleep 2000

$ kill %2
[2]  + 18745 terminated  nohup sleep 2000

$ jobs

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

Вы можете узнать больше об этих и других сигналах здесь или введя в терминале man signal или kill -l.

Терминальный мультиплексор

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

Терминальные мультиплексоры, такие как tmux позволяют вам мультиплексировать окна терминала с помощью панелей и вкладок, чтобы вы могли взаимодействовать сразу с несколькими сессиями оболочки. Более того, терминальные мультиплексоры позволяют вам отсоединять текущую сессию терминала и повторно подключаться к ней позднее. Это может значительно улучшить ваш рабочий процесс при работе с удаленными машинами, поскольку избавляет от необходимости использовать nohup и подобные трюки.

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

tmux ожидает от вас, что вы будете знать его комбинации клавиш. Все комбинации представлены в формате <C-b> x, что означает (1) нажать Ctrl+b, (2) отпустить Ctrl+b, (3) нажать x. tmux имеет следующую иерархию объектов:

Для дальнейшего чтения: здесь короткий туториал по tmux и здесь более детальный рассказ, который затрагивает оригинальную команду screen. Возможно вы также захотите познакомиться с командой screen, так как она является предустановленной в большинстве UNIX систем.

Псевдонимы

Ввод длинных команд может быть утомительным, включающие множество флагов или подробных опций. По этой причине большинство оболочек поддерживает создание псевдонимов (aliasing). Псевдоним (Alias) в оболочке — это краткая форма для другой команды, которую ваша оболочка автоматически заменит для вас. Например, псевдоним в bash имеет следующую структуру:

alias alias_name="command_to_alias arg1 arg2"

Заметьте, что вокруг знака = нет пробелов, т.к. alias является командой оболочки, принимающий один аргумент.

Псевдонимы имеют много удобных свойств

# Make shorthands for common flags
alias ll="ls -lh"

# Save a lot of typing for common commands
alias gs="git status"
alias gc="git commit"
alias v="vim"

# Save you from mistyping
alias sl=ls

# Overwrite existing commands for better defaults
alias mv="mv -i"           # -i prompts before overwrite
alias mkdir="mkdir -p"     # -p make parent dirs as needed
alias df="df -h"           # -h prints human readable format

# Alias can be composed
alias la="ls -A"
alias lla="la -l"

# To ignore an alias run it prepended with \
\ls
# Or disable an alias altogether with unalias
unalias la

# To get an alias definition just call it with alias
alias ll
# Will print ll='ls -lh'

Обратите внимание, что псевдонимы по умолчанию не сохраняются между сессиями оболочки. Чтобы сделать псевдоним постоянным, вам нужно включить его в файлы запуска оболочки, такие как .bashrc или .zshrc, которые мы представим в следующем разделе.

Dotfiles

Многие программы сконфигурированы с помощью обычных текстовых файлов, которые называются dotfiles (они названы так, потому что имена файлов начинаются с ., например ~/.vimrc, таким образом они по умолчанию скрыты в списке файлов ls).

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

Для bash редактирование .bashrc или .bash_profile будет работать в большинстве систем. Здесь вы можете объявлять команды, которые будут запущены при старте, например, определять псевдонимы или модифицировать переменную окружения PATH. На самом деле многие программы будут просить вас включить такую строчку export PATH="$PATH:/path/to/program/bin" в конфигурационный файл вашего терминала, чтобы терминал мог найти бинарные файлы программ.

Некоторые другие примеры инструментов, которые могут быть сконфигурированы с помощью dotfiles:

Как следует организовывать ваши dotfiles? Они должны находиться в своей собственной папке, под контролем версий, и ссылаться с помощью символических ссылок на места их использования с помощью скрипта. Это имеет следующие преимущества:

Что следует включать в ваши dotfiles?

Вы можете узнать о настройках контретного инструмента, прочитав онлайн документацию или man страницу. Ещё один отличный способ - поиск в интернете блог-постов о конкретных программах, где авторы расскажут вам о своих предпочтительных настройках. Ещё один способ узнать о настройках — это просмотреть dotfiles других людей: вы можете найти множество dotfiles репозиториев на Github — ищите самые популярные здесь (хотя мы не советуем слепо копировать все конфигурации). Здесб еще один отличный ресурс на эту тему.

Все инструкторы курса имеют свои dotfiles в открытом доступе на GitHub: Anish, Jon, Jose.

Переносимость

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

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

if [[ "$(uname)" == "Linux" ]]; then {do_something}; fi

# Check before using shell-specific features
if [[ "$SHELL" == "zsh" ]]; then {do_something}; fi

# You can also make it machine-specific
if [[ "$(hostname)" == "myServer" ]]; then {do_something}; fi

Если файл конфигурации это поддерживает, используйте include. Например ~/.gitconfig может содержать:

[include]
    path = ~/.gitconfig_local

Затем на каждой машине ~/.gitconfig_local может содержать специфичные настройки. YВы даже можете отслеживать эти настройки в отдельном репозитории для настроек, специфичных для каждой машины.

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

# Test if ~/.aliases exists and source it
if [ -f ~/.aliases ]; then
    source ~/.aliases
fi

Удаленные машины

Стало всё более обычным для программистов использовать удалённые серверы в своей повседневной работе. Если вам нужно использовать удалённые серверы для развертывания серверного программного обеспечения или вам нужен сервер с более высокими вычислительными возможностями, в итоге вы будете использовать Secure Shell (SSH). Как и в случае с большинством рассматриваемых инструментов, SSH имеет высокую степень настраиваемости, поэтому стоит изучить его.

Для подключения ssh к серверу вы выполняете следующую команду:

ssh foo@bar.mit.edu

Здесь мы пытаемся подключиться по ssh как пользователь foo к серверу bar.mit.edu. Сервер может быть указан как URL (bar.mit.edu) или IP (например foobar@192.168.1.42). Далее мы увидим, что если мы модифицируем конфигурационный файл ssh, можно будет подключаться просто используя ssh bar.

Исполнение команд

Часто упускаемой из виду особенностью ssh является возможность непосредственного выполнения команд. ssh foobar@server ls выполнит ls в домашней папке foobar. Это работает с использованием каналов, так что ssh foobar@server ls | grep PATTERN будет локально применять grep к удалённому выводу ls, а ls | ssh foobar@server grep PATTERN будет применять grep на удалённом сервере к локальному выводу ls.

SSH ключи

Аутентификация на основе ключей использует криптографию с открытым ключом, чтобы доказать серверу, что клиент владеет секретным закрытым ключом, не раскрывая сам ключ. Таким образом, вам не нужно каждый раз вводить свой пароль. Тем не менее, закрытый ключ (часто ~/.ssh/id_rsa или ~/.ssh/id_ed25519) фактически является вашим паролем, поэтому относитесь к нему соответствующе.

Генерация ключей

Для генерации пары ключей вы можете выполнить ssh-keygen.

ssh-keygen -o -a 100 -t ed25519 -f ~/.ssh/id_ed25519

Вам следует выбрать passphrase (фраза-пароль), чтобы избежать доступа к авторизованным серверам тех, кто получит ваш приватный ключ. Используйте ssh-agent или gpg-agent, чтобы вам не приходилось каждый раз вводить ваш passphrase.

Если вы когда-либо настраивали push функциональность на GitHub с использованием SSH-ключей, то, вероятно, вы уже выполнили шаги, описанные здесь, и у вас уже есть действующая пара ключей. Чтобы проверить, есть ли у вас passphrase и проверить её, вы можете выполнить ssh-keygen -y -f /path/to/key.

Аутентификация на основе ключа

ssh посмотрим .ssh/authorized_keys, чтобы определить, каких клиентов следует пропустить. Чтобы скопировать публичный ключ, вы можете использовать::

cat .ssh/id_ed25519.pub | ssh foobar@remote 'cat >> ~/.ssh/authorized_keys'

Более простое решение - использование ssh-copy-id:

ssh-copy-id -i .ssh/id_ed25519.pub foobar@remote

Копирование файлов через SSH

Есть много способов копировать файлы через SSH:

Перенаправление портов

Во многих сценариях вы столкнетесь с программным обеспечением, которое слушает определенные порты на машине. Когда это происходит на вашем локальном компьютере, вы можете ввести localhost:PORT или 127.0.0.1:PORT, но что делать, если программа запущена на удаленном сервере и у вас нет доступа к localhost удаленной машины через сеть/интернет?

Это называется перенаправлением портов (port forwarding) и бывает двух видов: Локальное перенаправление портов и Удаленное перенаправление портов (посмотрите на изображение для подробностей, авторство картинки принадлежит этому посту на StackOverflow).

Локальное перенаправление портов Локальное перенаправление портов

Удаленное перенаправление портов Удаленное перенаправление портов

Самый распространенный сценарий — это локальное перенаправление портов, когда сервис на удаленной машине слушает порт, и вы хотите связать порт на вашем локальном компьютере с удаленным портом. Например, мы выполняем jupyter notebook на удаленном сервере, который слушает порт 8888. Для перенаправления его на локальный порт 9999, мы бы сделали ssh -L 9999:localhost:8888 foobar@remote_server и затем перешли бы на localhost:9999 на нашем локальном компьютере.

Конфигурация SSH

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

alias my_server="ssh -i ~/.id_ed25519 --port 2222 -L 9999:localhost:8888 foobar@remote_server

Однако есть лучшая альтернатива - ~/.ssh/config.

Host vm
    User foobar
    HostName 172.16.174.141
    Port 2222
    IdentityFile ~/.ssh/id_ed25519
    LocalForward 9999 localhost:8888

# Configs can also take wildcards
Host *.mit.edu
    User foobaz

Дополнительным преимуществом использования файла ~/.ssh/config вместо псевдонимов заключается в том, что другие программы (например scp, rsync, mosh и т.д.) могут также читать эти конфигурации и конвертировать их в подходящие для себя флаги.

Обратите внимание, что файл ~/.ssh/config можно считать dotfile, и в общем случае его можно включать вместе с остальными вашими dotfiles. Однако, если вы делаете его публичным, подумайте о той информации, которую вы потенциально предоставляете незнакомцам в интернете: адреса ваших серверов, пользователи, открытые порты и т. д. Это может облегчить некоторые виды атак, поэтому будьте внимательны, делая общедоступной вашу конфигурацию SSH.

Серверные конфигурации указываются в /etc/ssh/sshd_config. Здесь вы можете вносить изменения, такие как отключение аутентификации по паролю, изменение портов ssh, включение перенаправления X11 и т. д. Вы можете указывать настройки конфигурации для каждого пользователя отдельно.

Разное

Общей проблемой при подключении к удаленному серверу являются отключения из-за выключения/перевода в спящий режим компьютера или смены сети. Более того, если у кого-то есть соединение с задержкой, использование ssh может стать затруднительным. Mosh - мобильная оболочка, которая улучшает ssh, позволяя подключения с роумингом, обрывание соединений и предоставляет интеллектуальный локальный эхо-ответ.

Иногда бывает удобно примонтировать удаленную папку. sshfs позволяет монтировать папку на удаленном сервере локально, и затем вы можете использовать локальный редактор.

Оболочки & Фреймворки

Во время работы с инструментами оболочки и скриптинга мы рассматривали оболочку bash, поскольку она наиболее распространена и в большинстве систем установлена по умолчанию. Тем не менее, это не единственный вариант.

Например, оболочка zsh является надмножеством bash и предлагает множество удобных функций “из коробки”, таких как:

Фреймворки также могут улучшить вашу оболочку. Некоторые из популярных фреймвоков: prezto или oh-my-zsh, также есть меньшие фреймворки, которые специфицируются на отдельных функциях, например zsh-syntax-highlighting или zsh-history-substring-search. Оболочки вроде fish iизначально включают множество этих удобных для пользователя функций. К некоторым из этих функций относятся:

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

Эмуляторы терминала

Вместе с настройкой вашей оболочки, стоит потратить некоторое время на выбор терминального эмулятора и его настроек. Существует множество терминальных эмуляторов (вот сравнение).

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

Упражения

Контроль задач

  1. Из того, что мы увидели, мы можем использовать команды вида ps aux | grep, чтобы получить pid интересующих задач, чтобы затем остановить их, но есть более предпочтительные способы сделать это. Начните команду sleep 10000 в терминале, приостановите ее с помощью Ctrl-Z и затем возобновите выполнение в фоновом режиме с помощью bg. Теперь используйте pgrep, чтобы найти pid задачи и затем pkill, чтобы остановить задачу без использования самого pid (Подсказка: используйте флаги -af).

  2. Скажем, вы не хотите начинать новый процесс, пока что другой не завершился, что бы вы сделали на этот счет? В данном упражнении ограничивающий нас процесс всегда будет sleep 60 &. Один из способов достигнуть этого - использовать команду wait. Попробуйте запустить команду sleep и пусть команда ls ждет, пока фоновый процесс завершится

    Однако такая стратегия провалится если мы попробуем выполнить команду в другой bash сессии, так как wait будет работать только для дочерних процессов. Одна возможность, которую мы не обсудили - команда kill будет иметь нулевой статус в случае успеха и ненулевой в обратном случае. kill -0 не посылает никакого сигнала, а просто возвращает ненулевой статус, если процесса не существует. Напишите bash функцию pidwait, которая берет pid и ждет, пока процесс не завершится. Вам следует использовать sleep, чтобы избежать расходования ресурсов CPU.

Терминальный мультиплексор

  1. Следуйте tmux туториалу и затем узнайте как делать базовые кастомизации с помощью этого туториала.

Псевдонимы

  1. Создайте псевдоним dc который вызывает cd (для случая, когда вы сделали ошибку при печати cd).

  2. Запустите history | awk '{$1="";print substr($0,2)}' | sort | uniq -c | sort -n | tail -n 10, чтобы получить top 10 самых используемых команд и подумайте над тем, чтобы сделать псевдонимы для них. Замечание: это работает для bash; если используете zsh, введите history 1 вместо history.

Dotfiles

Давайте введем вас в курс дела с dotfiles.

  1. Создайте папку для ваших dotfiles и настройте контроль версий.
  2. Добавьте конфигурацию для как минимум одной программы, например вашей оболочки с некоторыми персональными настройками (tдля начала это может быть что-то такое простое, как настройка приглашения оболочки, установив $PS1).
  3. Настройте метод быстрой установки ваших dotfiles (и без ручного вмешательства) на новой машине. Это может быть так же просто, как shell-скрипт, вызывающий ln -s для каждого файла, или вы можете использовать специальную утилиту.
  4. Протестируйте ваш скрипт установки на свежей виртуальной машине.
  5. Перенесите все текущие конфигурации инструментов в репозиторий ваших dotfiles.
  6. Опубликуйте ваши dotfiles на GitHub.

Удаленные машины

Установите виртуальную машину Linux (или используйте уже существующую) для этого упражнения. Если вы не знакомы с виртуальными машинами, ознакомьтесь с этим туториалом по установке.

  1. Перейдите в ~/.ssh/ и проверьте, есть ли у вас пара SSH-ключей. Если нет, сгенерируйте их с помощью ssh-keygen -o -a 100 -t ed25519. Рекомендуется использовать пароль и использовать ssh-agent, подробнее здесь.
  2. Отредактируйте .ssh/config чтобы в нем была следующая запись
Host vm
    User username_goes_here
    HostName ip_goes_here
    IdentityFile ~/.ssh/id_ed25519
    LocalForward 9999 localhost:8888
  1. Используйте ssh-copy-id vm, чтобы скопировать ваш ssh ключ на сервер.
  2. Запустите веб-сервер в вашей виртуальной машине, выполнив python -m http.server 8888. Доступ к веб-серверу VM осуществляется путем перехода на http://localhost:9999 с вашего компьютера.
  3. Отредактируйте конфигурацию вашего SSH-сервера, выполнив sudo vim /etc/ssh/sshd_config и отключите аутентификацию по паролю, изменив значение PasswordAuthentication. Отключите вход для root, изменив значение PermitRootLogin. Перезапустите ssh сервис с помощью sudo service sshd restart. Попробуйте снова подключиться через ssh.
  4. (Задача) Установите mosh в виртуальной машине и установите соединение. Затем отключите сетевой адаптер сервера/виртуальной машины. Сможет ли mosh правильно восстановиться после этого?
  5. (Задача) Изучите, что делают флаги -N и -f в ssh, и выясните, какая команда нужна для фонового перенаправления портов.

Редактировать страницу.

Лицензия CC BY-NC-SA.