Перейти к содержимому

Как запустить исполняемый файл в linux

  • автор:

Пошаговый запуск программы в Linux x86, или как добраться до main()?

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

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

Вот предстоящий нам маршрут:


Схема получена с помощью dot-фильтра, используемого для рисования направленных графов

К концу статьи вам все это станет понятно.

Как мы попадаем в main?

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

Если хотите, можете сохранить копию этой программы как prog1.c и повторять за мной. Первым делом я выполню ее сборку:

Прежде, чем переходить к отладке последующей версии этой программы ( prog2 ) в gdb , мы ее дизассемблируем и узнаем некоторые детали о процессе запуска. Я покажу вам вывод objdump -d prog1 , но не в порядке фактического вывода, а в порядке его выполнения. (В идеале вам следует сделать это самим. Например, сохранить копию с помощью objdump -d prog1 >prog1.dump , чтобы потом просмотреть ее в привычном редакторе).

Для начала разберемся, как мы попадаем в _start

При запуске программы оболочка или GUI вызывают execve() , которая выполняет системный вызов execve() . Если вы хотите побольше узнать об этом системном вызове, то просто введите в оболочке man execve . Он находится в разделе 2 мануала вместе со всеми остальными системными вызовами. Если кратко, то он настраивает стек и передает в него argc , argv и envp .

Дескрипторы файлов 0 , 1 и 2 ( stdin , stdout , stderr ) остаются со значениями, установленными для них оболочкой. Загрузчик проделывает много работы, настраивая релокации и, как мы увидим позднее, вызывая пре-инициализаторы. Когда все готово, управление передается программе через вызов _start() .

Вот соответствующий раздел objdump -d prog1 :

Операция XOR элемента с самим собой устанавливает этот элемент на нуль. Поэтому xor %ebp,%ebp устанавливает %ebp на нуль. Это предполагается спецификацией ABI (Application Binary Interface) для обозначения внешнего фрейма.

Далее мы извлекаем верхний элемент стека. Здесь у нас на входе argc , argv и envp , значит операция извлечения отправляет argc в %esi . Мы планируем просто сохранить его и вскоре вернуть обратно в стек. Так как argc мы извлекли, %esp теперь указывает на argv . Операция mov помещает argv в %ecx , не перемещая указатель стека.

Теперь мы выполняем для указателя стека операцию and с маской, которая обнуляет нижние четыре бита. В зависимости от того, где находился указатель, он переместится ниже на величину от 0 до 15 байт, что приведет к выравниванию кратно 16 байтам. За счет подобного выравнивания элементов стека повышается эффективность обработки памяти и кэша. В частности, это необходимо для SSE (Streaming SIMD Extensions), инструкций, способных одновременно обрабатывать вектора с плавающей точкой одинарной точности.

В конкретно этом случае %esp на входе в _start имел значение 0xbffff770 . После того, как мы извлекли argc , %esp стал 0xbffff774 , то есть сместился на более высокий адрес (добавление элементов в стек ведет к перемещению по памяти вниз, а их извлечение – вверх). После выполнения and значение указателя стека вновь стало 0xbffff770 .

Далее устанавливаем значения для вызова __libc_start_main

Теперь мы начинаем передавать в стек аргументы для _libc_start_main . Первый, %eax , является мусором, который передается только потому, что в стек мы собираемся поместить 7 элементов, а для 16-байтового выравнивания требуется 8-й. Использоваться он не будет. Сама функция _libc_start_main линкуется из glibc . В дереве исходного кода glibc она находится в файле csu/libc-start.c . Определяется _libc_start_main так:

Итак, мы ожидаем, что _start передаст обозначенные аргументы в стек в обратном порядке до вызова _libc_start_main .

Содержимое стека перед вызовом __libc_start_main

__libc_csu_fini линкуется в наш код из glibc и находится в файле csu/elf-init.c дерева исходного кода. Это деструктор нашей программы на уровне Си, и чуть позже я разберу его подробно.

Так, а где переменные среды?

Вы заметили, что мы не получили из стека envp , указатель на наши переменные среды? Среди аргументов _libc_start_main его тоже нет. Но мы знаем, что main называется int main(int argc, char** argv, char** envp) , так в чем же дело?

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

После установки envp функция _libc_start_main использует тот же трюк и…вуаля! Сразу за завершающим нулем в конце массива envp находится очередной вектор, а именно вспомогательный вектор ELF, который загрузчик использует для передачи процессу определенной информации. Для просмотра его содержимого достаточно просто установить перед запуском программы переменную среды LD_SHOW_AUXV=1 . Вот результат для нашей prog1 :

Разве не интересно? Тут полно всяческой информации.

  • Здесь мы видим AT_ENTRY , представляющую адрес _start , где находится наш userid , действующий userid и groupid .
  • Очевидно, что используется платформа 686 , а частота times() равна 100 тактов/с.
  • AT_PHDR указывает расположение ELF-заголовка программы, в котором хранится информация о нахождении всех сегментов этой программы в памяти, а также о записях релокаций и всем остальном, что нужно знать загрузчику.
  • AT_PHENT – это просто количество байт в записи заголовка.

__libc_start_main в общем

На этом я закончу разбор деталей _libc_start_main и лишь добавлю, что в общем он:

  • Реализует функционал безопасности с помощью вызовов setuid и stgid ;
  • Запускает потоковую обработку;
  • Регистрирует аргументы fini (для нашей программы) и аргументы rtld_fini (для загрузчика среды выполнения), которые запустит at_exit для выполнения процедур очистки программы и загрузчика.
  • Вызывает аргумент init ;
  • Вызывает main с передаваемыми ей аргументами argc и argv , а также аргументом global_environ , о чем я писал выше;
  • Вызывает exit с возвращаемым main значением.

Вызов аргумента init

Аргумент init для _libc_start_main устанавливается на _libc_csu_init , который также линкуется в наш код. Он компилируется из программы Си, расположенной в файле csu/elf-init.c дерева исходного кода glibc , и линкуется в нашу программу. Его код Си похож на (но содержит намного больше #ifdef )…

Конструктор нашей программы

_libc_csu_init для нашей программы очень важен, так как конструирует ее исполняемый файл. Я уже слышу, как вы говорите: «Это же не C++!». Все верно, но принцип конструкторов и деструкторов не принадлежит к C++ и предшествовал этому языку.

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

Что такое thunk?

Говорить здесь особо не о чем, но я подумал, что вы захотите это увидеть. Функция get_pc_thunk весьма интересна. Она вызывается для настройки позиционно-независимого кода. Чтобы все сработало, указатель базы должен иметь адрес GLOBAL_OFFSET_TABLE . Соответствующий код выглядел так:

Посмотрим на происходящее подробнее. Вызов _get_pc_thunk_bx , как и любой другой, помещает в стек адрес следующей функции, чтобы при возвращении выполнение продолжилось с очередной инструкции. В данном случае нам нужен тот самый адрес. Значит, в _get_pc_thunk_bx мы копируем адрес возврата из стека в %ebx . Когда происходит возврат, очередная инструкция прибавляет к нему _GLOBAL_OFFSET_TABLE_ , разрешаясь в разницу между текущим адресом и глобальной таблицей смещений, используемую позиционно-независимым кодом.

В этой таблице хранится набор указателей на данные, к которым мы хотим обратиться, и нам лишь нужно знать их смещения. При этом загрузчик сам фиксирует для нас нужный адрес. Для обращения к процедурам существует аналогичная таблица. Было бы поистине утомительно программировать подобным образом в ассемблере, но можно просто написать нужный код на Си/С++ и передать аргумент -pic компилятору, который сделает это автомагически.
Встречая данный код в ассемблере, вы можете сделать вывод, что исходник был скомпилирован с флагом -pic .

Но что это за цикл?

Цикл из _libc_csu_init мы рассмотрим сразу после вызова init() , который фактически вызывает _init . Пока же просто имейте ввиду, что он вызывает для нашей программы любые инициализаторы уровня Си.

Вызов _init

Хорошо. Загрузчик передал управление _start , которая вызвала _libc_start_main , которая вызвала _libc_csu_init , которая теперь вызывает _init :

Начинается она с регулярного соглашения о вызовах Си

Если вы хотите побольше узнать об этом соглашении, почитайте Basic Assembler Debugging with GDB. Если коротко, то мы сохраняем указатель базы вызывающего компонента в стеке и направляем указатель базы на верхушку стека, после чего резервируем место для своего рода 4-байтовой локальной переменной.

Интересен здесь первый вызов. Его задача во многом аналогична вызову get_pc_thunk , который мы видели ранее. Если посмотреть внимательно, то он направлен к следующему по порядку адресу. Это переносит нас к очередному адресу, как если бы мы просто продолжили, но при этом в качестве побочного эффекта данный адрес оказывается в стеке. Он помещается в %ebx , а затем используется для установки доступа к глобальной таблице доступа.

Покажи мне свой профиль

Далее мы захватываем адрес gmon_start . Если он равен нулю, то мы его просто проскакиваем. В противном случае он вызывается для запуска профилирования. В этом случае происходит запуск процедуры для начала профилирования и вызов at_exit , чтобы по завершению сработала другая процедура и записала gmon.out .

Вызов frame_dummy

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

Переходим к конструкторам!

В завершении мы вызываем _do_global_ctors_aux . Если у вас сложности с программой, которые возникают до запуска main , то искать, возможно, нужно именно здесь. Конечно, сюда помещаются конструкторы для глобальных объектов С++, но кроме них тут могут находиться и другие компоненты.

Создадим пример

Теперь давайте изменим prog1 , создав prog2 . Самая интересная часть – это __attribute__ ((constructor)) , который сообщает gcc , что компоновщик должен поместить соответствующий указатель в таблицу, используемую _do_global_ctors_aux . Как видите, наш фиктивный конструктор выполняется. (компилятор заполняет _FUNCTION_ именем функции. Это магия gcc ).

_init в prog2 практически не изменяется

Чуть позже мы подключим к процессу gdb и разберем эту программу. Ну а пока же рассмотрим ее _init .

Как видите, адреса немного отличаются от prog1 . Похоже, дополнительный элемент данных сместил все на 28 байт. Итак, здесь у нас имена двух функций, a_constructor (14 байт с завершающим нулем) и main (5 байт с завершающим нулем), а также две форматирующих строки %s\n (2*4 байта с символом переноса строки и завершающим нулем).

Итого получается 14+5+4+4 = 27. Хмм…одного не хватает. Хотя это просто предположение, проверять я не стал. Позже мы все равно сделаем остановку на вызове _do_global_ctors_aux , сделаем один шаг и проанализируем происходящее.

А вот и код, который будет вызван

Чисто в качестве подсказки приведу код для _do_global_ctors_aux , взятый из файла gcc/crtstuff.c исходного кода gcc .

Как видите, он инициализирует p из глобальной переменной _CTOR_END_ и вычитает из нее 1 . Напомню, что это арифметика указателей, а указатель указывает на функцию, значит в данном случае -1 приводит к смещению на один указатель функции назад или на 4 байта. Мы также увидим это в ассемблере.

Несмотря на то, что указатель не имеет значения -1 (приведение к указателю), мы вызовем функцию, на которую указываем, после чего снова переведем указатель функции назад. Очевидно, что таблица начинается с -1 , после чего идет некоторое количество (возможно даже 0) указателей функции.

То же самое в ассемблере

Вот код ассемблера, соответствующий полученному из objdump -d результату. Прежде, чем переходить к трассировке с помощью отладчика, мы внимательно по нему пройдемся, чтобы вам было понятнее.

Сначала пролог

Здесь у нас типичный пролог с добавлением резервирования %ebx , так как мы собираемся использовать его в функции. Помимо этого, мы резервируем место для указателя p . Вы заметите, что несмотря на резервирование под него места в стеке, хранить мы его там не будем. Вместо этого p будет размещаться в %ebx , а *p в %eax .

Далее подготовка к циклу

Похоже, произошла оптимизация. Вместо загрузки _CTOR_END_ с последующим вычитанием из него 1 и разыменовыванием мы переходим далее и загружаем *(__CTOR_END__ — 1) , который представляет непосредственное значение 0x8049f14 . Его значение мы помещаем в %eax (помните, что инструкция $0x8049f14 означала бы помещение этого значения, а ее вариант без $ — помещение содержимого этого адреса).

Следом мы сравниваем это первое значение с -1 , и если они равны, то заканчиваем и переходим к адресу 0x8049f14 , где очищаем стек, извлекая все сохраненные в нем элементы и делая возврат.

Предполагая, что в таблице функций есть хотя бы один элемент, мы также перемещаем непосредственное значение $8049f14 в %ebx , который является нашим указателем функции f , после чего выполняем xchg %ax,%ax .

Что это вообще такое? Эта команда используется в качестве NOP (No OPeration) в 16- и 32-битных x86. По факту она ничего не делает. В нашем случае ее задача в том, чтобы цикл (верхняя его часть – это вычитание на следующей строке) начинался не с 8048466 , а с 8048468 . Смысл здесь в выравнивании начала цикла с 4-байтовой границей, в результате чего весь цикл с большей вероятностью впишется в одну строку кэша, не разбиваясь на две. Это все ускорит.

И вот мы у вершины цикла

Далее мы вычитаем 4 из %ebx , подготавливаясь к очередному циклу, вызываем функцию, адрес которой получили в %eax , перемещаем следующий указатель функции в %eax и сравниваем его с -1 . Если они не равны, возвращаемся к операции вычитания и повторяем цикл.

Эпилог

В противном случае мы достигаем эпилога функции и возвращаемся к _init , которая сразу достигает своего эпилога и возвращается к _libc_csu_init_ , о котором вы уже наверняка забыли. Здесь все еще остается один цикл для завершения, но сначала…

Как я и обещал, мы займемся отладкой prog2 .

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

Мы запустили программу в отладчике, включили disassemble-next-line , чтобы он всегда показывал очередную строку в дизассемблированном виде, и установили точку останова на строку в _init , где будем вызывать _do_global_ctors_aux .

Здесь я ввел r , чтобы запустить программу и достичь точки останова. Очередной командой для gdb стала si , инструкция шага, указывающая отладчику шагнуть на одну инструкцию вперед.

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

Хорошо, с прологом мы закончили, пришло время реального кода.

После загрузки указателя мне стало любопытно, и я ввел p/x $eax , то есть попросил gdb вывести hex-содержимое регистра %eax . Это не -1 , значит можно предположить, что цикл мы проходим.

Теперь, поскольку последней командой был вывод, я не могу повторить si нажатием Ввода, и мне придется ее ввести.

Вот здесь очень интересно. Мы шагнули в вызов и теперь находимся в функции a_constructor . Поскольку у gdb есть для нее исходный код, он показывает исходник Си для следующей строки. А так как я включил disassemble-next-line , он также покажет нам соответствующий код ассемблера.

В данном случае это пролог функции, и мы получаем все его три строки. Разве не интересно? Далее я переключусь на команду n (next), потому что скоро покажется printf . Первая n пропустит пролог, вторая printf , а третья эпилог. Если вас когда-нибудь интересовало, почему при пошаговом продвижении с помощью gdb нужно делать дополнительный шаг в начале и конце функции, то теперь вы знаете почему.

Мы переместили адрес строки a_constructor в стек в качестве аргумента для printf , но он вызывает puts, поскольку компилятор догадался, что нас интересует только puts .

Раз мы трассируем программу, то она, естественно, выполняется, в связи с чем выше мы видим вывод a_constructor . Закрывающая скобка > соответствует эпилогу, поэтому он выводится сейчас. К слову отмечу, если вам не знакома инструкция leave , то выполняет она то же, что и:

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

Мне снова стало интересно, и я решил еще раз проверить значение указателя функции. На этот раз он равен -1 , значит из цикла мы выходим.

Заметьте, что мы снова вернулись в _init .

Обратите внимание, что мы перепрыгнули обратно к _libc_csu_init , и здесь я ввел q для выхода из gdb . Вот и вся отладка, которую я обещал.

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

Возвращаемся в __libc_csu_init__

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

Еще один цикл вызова функции

Что такое массив _init_ ? Я уж думал, вы и не спросите. На этом этапе вы также можете выполнять код. Поскольку идет он сразу после возвращения из _init , которая запускала наши конструкторы, то содержимое этого массива будет выполняться после завершения конструкторов. Вы можете сообщить компилятору, что хотите выполнить на этом этапе функцию, которая в результате получит те же аргументы, что и main .

Мы пока этого делать не будем, потому что есть и другие подобные моменты. Давайте просто вернем результат из _lib_csu_init . Помните, куда это нас приведет?

Мы вернемся аж к __libc_start_main__

Теперь он вызывает наш main , а результат передает в exit() .

exit() выполняет функции, зарегистрированные с помощью at_exit в порядке их добавления. Затем она выполняет очередной цикл функций, на этот раз из массива fini . Далее она выполняет еще один цикл функций, теперь уже деструкторов. (В реальности она находится во вложенном цикле и работает с массивом списков функций, но поверьте мне, завершаются они именно в этом порядке). Вот смотрите.

Эта программа, hooks.c, связывает все воедино

Если собрать и выполнить эту программу (я зову ее hook.c ), то выводом будет:

Конец

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

Как запускаются исполняемые файлы в Linux

исполняемые файлы в linuxДоброго времени, читатели моих постов о Linux!

В сегодняшней статье расскажу о том, как работают исполняемые файлы. Из моей прошлой статьи о атрибутах доступа к файлам в Linux думаю Вам будет известно, что такое полномочия выполнения (исполнения). Данное право можно установить для любого файла. Исходя из этого, можно задать вопрос: неужели любой файл можно сделать программой? Да, так и есть. В Linux является ли файл исполняемым или нет, определяется не по его расширению, как в Windows (понятие расширение файла отсутствует в файловой системе Linux), а по правам доступа. Если у файла установлено право x (выполнения), его можно запустить на выполнение.

Что происходит, когда мы пытаемся выполнить файл ? Мы пытаемся набрать имя и, может быть, путь к файлу, который пытаемся запустить в командной строке и нажимаем Enter. (если файл расположен в текущем каталоге, то необходимо набирать ./ program). В первую очередь, оболочка проверяет, а имеет ли пользователь права на исполнение этого файла? Если имеет, тогда система смотрит, а это исполняемый бинарный файл? В Linux все исполняемые бинарные файлы в начале файла имеют заголовок .ELF (Executable and Linkable Format) (напомню, что в Windows в исполняемых файлах заголовок — MZ). Если это исполняемый бинарный файл, тогда, согласно его заголовку, происходит распределение оперативной памяти, и управление передается программе.

Если файл не бинарный, тогда считается, что это текстовый файл — скрипт или сценарий. В первых двух байтах сценария обнаруживается последовательность символов #!. Если символы «#!» присутствуют, тогда всю первую строку сценария, начиная с третьего байта, ядро воспримет как команду обработки. Исполнение сценария, содержащего указанную последовательность приведет к запуску указанной после » #!» команды, последним параметром которой будет имя самого файла сценария. Например, для файлов, написанных на языке shell script, первая строка будет выглядеть так:
#! /bin/sh

Для программ, написанных на perl, так:
#! /bin/perl

Таким образом, можно написать сценарий для любой программы, пример:

Во всех интерпретируемых языках программирования # — это символ комментария. То есть первая строка считается комментарием и программой не выполняется. При указании интерпретатора можно писать аргументы командной строки. Например:
#! /bin/sed -f command

Если в файле в первой строке нет этих символов, тогда все зависит о программы оболочки, в которой запускается программа. Если используется bash, то он считает, что файл содержит программу, написанную на языке shell script, запускает копию себя любимого и передает этой копии файл на интерпретацию. Если в файле действительно находится программа, то он ее выполняет. Если в файле находится «Война и мир» графа Льва Николаевича Толстого, то на экране появляются сообщения об ошибках shell script: «Я не знаю оператор Пьер Безухов. Наташа Ростова — это оператор или функция?»

Если Вы желаете выполнить exe-файл, который запускали в Windows, необходимо воспользоваться таким пакетом, как Wine. Но это уже совсем другая тема.

k1r8r0wn / linux_tips.md

Прогр1 | Прогр2 | . | ПрогрN — передать stdout Прогр1 в качестве stdin для Прогр2, далее stdout Прогр2 в качестве stdin для Прогр3 и т.д.

5. Скачивание файлов из интернета

wget ссылка — скачать файл по ссылке и сохранить в текущей директории

wget -P путь_до_директории ссылка — скачать файл по ссылке и сохранить в директории заданной путем

wget -O путь_до_файла ссылка — скачать файл по ссылке и сохранить под указанным именем

wget -c ссылка — докачать файл по ссылке в случае обрыва связи

wget —spider ссылка — проверить доступность файла по ссылке

wget -i текстовый_файл — скачать несколько файлов по ссылкам из текстового файла

wget -r -l глубина ссылка — рекурсивное скачивание файлов по ссылке на указанную глубину (по умолчанию глубина 5)

wget -r -A тип,тип. тип ссылка — рекурсивное скачивание файлов только определенного типа (типов)

6. Работа с архивами

unzip архив.zip — распаковать содержимое архива.zip

gunzip архив.gz — распаковать содержимое архива.gz, файл архив.gz удалить

tar -xvf архив.tar — распаковать архив.tar

tar -xzvf архив.tar.gz — распаковать архив.tar.gz (с использованием gunzip)

zip архив.zip файл1 файл2 . — запаковать перечисленные файлы и/или папки в архив.zip

gzip файл — запаковать файл в файл.gz, исходный файл удалить

tar -cvf архив.tar файл1 файл2 . — запаковать перечисленные файлы и/или папки в архив.tar (без сжатия)

gzip архив.tar — запаковать архив.tar в архив.tar.gz, исходный архив.tar удалить

tar -zcvf архив.tar.gz файл1 файл2 . — запаковать перечисленные файлы и/или папки в архив.tar.gz (c сжатием при помощи gzip)

Еще один архиватор:

tar -cjvf архив.tar.bz2 файл1 файл2 .

tar -xjvf архив.tar.bz2

Сжатие/распаковка без удаления:

gzip -c файл > файл.gz

gunzip -c файл.gz > файл

bzip2 -c файл > файл.bz2

bunzip2 -c файл.bz2 > файл

7. Поиск файлов и слов в файлах

find <папка> -name “<имя файла>” — найти указанный файл в папке

/ -name “file.txt” — найти file.txt в домашней директории

/ -name “*.txt” — найти все текстовые файлы в домашней директории

grep “<строка>” <файл> — найти строку в файле

grep -с “<строка>” <файл> — посчитать количество вхождений строки

grep -r “<строка>” <папка> — найти строку во всех файлах в папке

grep “hello” file.txt — найти “hello” в файле file.txt

grep -с “123” file.txt — вывести количество раз, которое “123” встречается в file.txt

/ — найти “world” во всех файлах в домашней директории

7*. Продвинутый поиск и редактирование

Поиск:

find -iname “<имя файла>” — не учитывать регистр

find -path “<путь>” — найти указанный путь

find -size <размер> — выводить файлы указанного размера

find -maxdepth <число> — искать не больше чем на заданное число уровней вниз

find -mindepth <число> — искать начиная с заданного числа уровней вниз

grep -l “<строка>” <файл> — список файлов с этой строкой

grep -L “<строка>” <файл> — список файлов, где этой строки нет

grep -n “<строка>” <файл> — выводить номер строки в файле

grep -m <число> “<строка>” <файл> — не искать дальше после заданного числа вхождений

grep -A <число> “<строка>” <файл> — выводить это число строк после вхождения

grep -B <число> “<строка>” <файл> — выводить это число строк до вхождения

grep -C <число> “<строка>” <файл> — выводить это число строк вокруг вхождения

grep -E “<шаблон>” <файл> — найти указанный шаблон в файле

grep -E “^go” <файл> — найти строки, начинающиеся с “go”

grep -E “go$” <файл> — найти строки, оканчивающиеся на “go”

grep -E “c[au]t” <файл> — найти все слова, содержащие cut и cat

grep -E “ [a-z]ight ” <файл> — слова из 5 букв, кончающиеся на “ight”

grep -E “ [a-z]*ight ” <файл> — слова из 4 и более букв, кончающиеся на “ight”

grep -E “ [a-z]+ight ” <файл> — слова из 5 и более букв, заканчивающиеся на “ight”

grep -E “ [a-z]?ight ” <файл> — слова из 4-5 букв, заканчивающиеся на “ight”

grep -E “ [a-zA-Z]*ight ” <файл> — слова, заканчивающиеся на “ight” (разрешены большие буквы)

cat <файл> | sed ‘инструкция’ sed ‘инструкция’ <файл> — потоковый редактор: читает строчки из stdin (или из файла), обрабатывает их по инструкции и пишет в stdout

Если хотим писать в файл:

> <файл> — обычное перенаправление

-i, —in-place — перезаписать входной файл

Замена:

sed ‘s/John/Nick/g’ old.txt > new.txt — заменить все John на Nick

sed -r ‘s/J[a-z]*n/Nick/g’ old.txt > new.txt — заменить все слова, которые начинаются на J и заканчиваются на n на Nick

sed -n ‘2,4p’ file.txt — вывести строки с 2 по 4

sed ‘2,4d’ file.txt — вывести все строки кроме 2-4

sed -n ‘/[0-9]\<2\>/p’ file.txt — вывести строки с 2 цифрами подряд

sed ‘2,/[Rr]ight/d’ file.txt — вывести все строки кроме со 2 до строки содержащей “right” (с большой или маленькой буквы)

Посчитать что-то в файле:

wc [что-считаем] <путь> wc -l file.txt

Сравнить файлы/директории:

diff [-q -r] <путь1> <путь2> diff file1.txt file2.txt | less diff -qr dir1/ dir2/

Узнать сколько места занимаем на диске:

du [—max-depth <глубина> -h] <путь> du -h

du –-max-depth 1 -h .

df [-h] — узнать сколько места занято/свободно во всей системе

Вывести часть файла:

head [-n <количество строк>] <путь> tail [-n <количество строк>] <путь>

head -n 10 file.txt

tail -n 50 file.txt | less

Работа с файлами/директориями:

Вывод с сортировкой:

ls —sort=[вид сортировки] -l <путь> ls –-sort=size -l

Перенаправление в один файл:

Перенаправление одного потока в другой:

2>&1 — stderr в stdout

1>&2 — stdout в stderr

Перенаправление в никуда и из ниоткуда:

cat /dev/null > file.txt

Команда входа: ssh логин@адрес_сервера -p порт

Создание ключа: ssh-keygen

Сообщить системе о ключе: ssh-add

Просмотр публичного ключа: cat

Редактирование авторизованных ключей (на сервере): nano

10. Обмен файлами

Копирование файлов:

scp -P порт логин@адрес_сервера:путь1 путь2 — с сервера (путь1) на клиента (путь2)

scp -P порт путь1 логин@адрес_сервера:путь2 — с клиента (путь1) на сервер (путь2)

11. Установка и обновление программ

Установка программ через терминал: sudo apt-get install программа

Удаление программ через терминал: sudo apt-get remove программа

Обновление ссылок на пакеты: sudo apt-get update

Обновление установленных пакетов: sudo apt-get upgrade

Обновление отдельной программы: sudo apt-get install —only-upgrade программа

12. Запус приложений, контроль запускаемых программ

Ctrl + C — прервать выполнение

<Ctrl + Z> — приостановить выполнение:

fg — продолжить (foreground)

bg — продолжить в фоновом режиме (background)

jobs — посмотреть запущенные программы

fg %<номер> — продолжить программу с этим номером

bg %<номер> — продолжить программу с этим номером в фоновом режиме

ps — посмотреть ваши процессы

top — отслеживать процессы в реальном времени

top -u <имя пользователя> — отслеживать процессы этого пользователя

kill <номер процесса> — завершить процесс с этим номером

kill -9 <номер процесса> — “убить” процесс с этим номером

13. Многопоточные приложения

free -g — информация об оперативной памяти

nproc — количество ядер процессора

lscpu — детальная информация о процессоре

14. Менеджер терминалов tmux

<Ctrl + Shift + T> — открыть новую вкладку в терминале

Alt + <цифра> — перейти в указанную вкладку

<Ctrl + Shift + W> — закрыть текущую вкладку

tmux — запустить tmux

<Ctrl + B> — перейти в режим команд

<Ctrl + B> и C (зажать <Ctrl+B>, отпустить, затем нажать С) — создать новую вкладку

<Ctrl + B> и <цифра> — перейти в указанную вкладку

<Ctrl + B> и N / <Ctrl + B> и P — перейти в следующую / предыдущую вкладку

<Ctrl + B> и X (или exit) — закрыть вкладку

<Ctrl + B> и D — временно выйти из tmux

tmux attach / tmux a — вернуться в tmux

tmux list-sessions — посмотреть список запущенных tmux’ов

Как запустить программу на Linux

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

Дальше мы рассмотрим виды программ, их запуск программ на Linux различными способами и другие полезные для новичков вещи, опытным пользователям это все и так уже известно.

Виды программ в Linux

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

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

  • Бинарные программы — содержат инструкции процессору уже готовые к выполнению, большинство программ находятся в таком формате, они быстрые и выполняются сразу же системой;
  • Программы на байт-коде — это уже не процессорные инструкции, а инструкции определенной виртуальной машины, которая может их выполнять, без виртуальной машины такие команды не могут быть выполнены. Такие программы потребляют больше ресурсов, но тоже достаточно быстрые, их преимущество в том, что они могут выполняться без изменения везде где может работать виртуальная машина. К таким программам можно отнести программы на Java.
  • Скриптовые программы — эти программы состоят из набора команд в виде обычного текста, которые выполняет специальный интерпретатор. Такие программы более медленные, но зато они проще в разработке и их код можно легко и быстро изменить.

А теперь перейдем к запуску программ.

Запуск программ в терминале

Изначально в операционных системах Unix и Linux не было графического интерфейса, поэтому программы запускались командами из терминала. Сейчас это тоже возможно и достаточно активно используется опытными пользователями. Синтаксис запуска программы выглядит таким образом:

/путь/к/файлу/программы параметры

Параметры указываются только, когда они нужны, но всегда оболочка должна знать полный путь к программе. Все что после имени программы и пробела — это параметры. Вы, наверное, уже заметили, что обычно мы не указываем полный путь при выполнении программ. Это было бы очень долго и неудобно.

Разработчики придумали обходной путь. Была создана переменная PATH, в которой хранятся все пути к папкам где обычно находятся программы — /bin, /sbin, /usr/bin, /usr/sbin и так далее. Вы можете посмотреть ее содержимое командой:

Когда вы набираете имя программы система ищет исполняемый файл с таким именем по всем папкам из PATH и если находит — то выполняет. Если же такого файла нет, то выдается сообщение — command not found. Таким образом, чтобы запустить одну из системных программ достаточно набрать имя ее исполняемого файла, например:

И можно передать параметры после пробела:

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

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

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

имя_переменной = значение команда

По умолчанию эта команда открывает настройки утилиты sudo в редакторе Vim, но с этой переменной окружения настройки откроются в редакторе nano.

Запуск программ от имени другого пользователя

Вы уже знаете как запустить программу в терминале linux, а что насчет других пользователей? В Windows достаточно часто используется запуск программ от имени администратора чтобы программа могла получить больше прав доступа в системе. В Linux для этого используется утилита sudo. Ее имя можно расшифровать как switchuserdo — изменить пользователя и выполнить. По умолчанию утилита выполняет команду от имени суперпользователя root:

sudo команда
sudo whoami

Но с помощью опции -u можно выполнить программу от имени любого пользователя, зарегистрированного в системе:

sudo -u имя_пользователя команда
sudo -u postgres whoami

Команда whoami (кто я) выводит имя текущего пользователя.

Как запустить программу в фоне

Иногда возникает необходимость запустить долго выполняющуюся программу в терминале так, чтобы она не мешала дальше работать. Для этого можно использовать запуск программы в фоновом режиме linux:

dd if=/dev/zero of=

Система выведет PID, уникальный идентификатор программы, который вы потом можете использовать чтобы закрыть ее командой kill:

Как запустить скрипт в Linux

Мы уже говорили, что программы делятся на бинарные и интерпретируемые. Раньше мы говорили только про бинарные программы. Для запуска интерпретируемых нужен непосредственно интерпретатор, к таким программам относятся написанные на таких языках, как Java, Python, Perl, Ruby, PHP, NodeJS и многих других. Синтаксис запуска такой программы отличается:

интерпретатор /путь/к/файлу/программы параметры

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

А Java программы нужно запускать так:

java -jar program.jar

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

Или же просто набрать путь к скрипту:

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

sudo chmod u+x ./script.sh

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

Запуск программ Linux в графическом интерфейсе

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

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

Точно так же работает запуск скриптов в графическом интерфейсе. Вы можете найти все ярлыки из меню в каталоге /usr/share/applications/. Любую программу можно запустить двойным щелчком отсюда. Но давайте посмотрим что находится внутри ярлыка, для этого откройте его в текстовом редакторе:

Кроме всего прочего, в строке Exec указана команда, которая выполняет запуск программы linux, когда вы делаете двойной клик на ярлыке. Вы можете взять один из существующих ярлыков и сделать на его основе свой. Здесь указано просто имя программы. Но важно заметить, что лучше указывать полный путь в таких местах, как ярлыки, скрипты, cron и так далее это уменьшит количество ошибок, поскольку вы не можете знать проверяет ли система в этом случае PATH или ищет программу только в текущем каталоге. Теперь вы знаете все о том как запустить программу на linux.

Выводы

В этой статье мы рассмотрели как запустить программу через терминал ubuntu или в других дистрибутивах Linux. Несмотря на то, что это кажется очень простой темой, тут есть свои интересные моменты, которые могут быть полезны. Но вы о них уже знаете. Если у вас остались вопросы, спрашивайте в комментариях!

Обнаружили ошибку в тексте? Сообщите мне об этом. Выделите текст с ошибкой и нажмите Ctrl+Enter.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

https://czena.vyvod-iz-zapoya-na-domu-voronezh.ru/