Окончательный ответ на вопрос "как запускаются программы" в Linux - это пара статей на LWN.net, озаглавленных, как ни странно, Как запускаются программы и Как запускаются программы: Двоичные файлы ELF. В первой статье скрипты рассматриваются кратко. (Строго говоря, окончательный ответ находится в исходном коде, но эти статьи легче читать и они содержат ссылки на исходный код.)
Небольшие эксперименты показывают, что вы почти все правильно поняли, и что выполнение файла, содержащего простой список команд, без шебанга, должно обрабатываться оболочкой. В execve(2) manpage содержится исходный код тестовой программы execve; мы воспользуемся ею, чтобы посмотреть, что происходит без оболочки. Сначала напишите тестовый скрипт, testscr1
, содержащий
#!/bin/sh
pstree
и другой, testscr2
, содержащий только
pstree
Сделайте их оба исполняемыми и проверьте, что они оба запускаются из оболочки:
chmod u+x testscr[12]
./testscr1 | less
./testscr2 | less
Теперь попробуйте снова, используя execve
(предполагая, что вы создали его в текущем каталоге):
./execve ./testscr1
./execve ./testscr2
testscr1
по-прежнему запускается, но testscr2
производит
execve: Exec format error
Это показывает, что оболочка обрабатывает testscr2
по-другому. Однако он не обрабатывает сам сценарий, для этого по-прежнему используется /bin/sh
; это можно проверить, переведя testscr2
в less
:
./testscr2 | less -ppstree
На моей системе я получаю
|-gnome-terminal--+-4*[zsh]
| |-zsh-+-less
| | `-sh---pstree
Как вы можете видеть, есть оболочка, которую я использовал, zsh
, которая запустила less
, и вторая оболочка, обычная sh
(dash
на моей системе), для запуска сценария, который запустил pstree
. В zsh
этим занимается zexecve
в Src/exec. c
: оболочка использует execve(2)
, чтобы попытаться выполнить команду, и если это не удается, она читает файл на предмет наличия в нем shebang, обрабатывая его соответствующим образом (что также будет сделано ядром), и если это не удается, она пытается запустить файл с помощью sh
, если из файла не был прочитан ни один нулевой байт:
for (t0 = 0; t0 != ct; t0++)
if (!execvebuf[t0])
break;
if (t0 == ct) {
argv[-1] = "sh";
winch_unblock();
execve("/bin/sh", argv - 1, newenvp);
}
bash
имеет такое же поведение, реализованное в execute_cmd. c
с полезным комментарием (на который указал taliezin):
Выполнить простую команду, которая, надеюсь, определена в дисковом файле где-то.
fork ()
- connect pipes
- look up the command
- do redirections
execve ()
- Если
execve
не удалось, посмотрите, установлен ли в файле исполняемый режим. Если да, и это не каталог, то выполните его содержимое как сценарий оболочки.
POSIX определяет набор функций, известных как функции exec(3)
, которые обертывают execve(2)
и обеспечивают эту функциональность; подробности см. в ответе muru. В Linux, по крайней мере, эти функции реализуются библиотекой C, а не ядром.
sed -i -e '/^#!/p' -e '/^#/d' file
Это будет проходить через файл построчно и находит строку, начинающуюся с #!
будет напечатано первым выражением. Затем он будет удален из пространства шаблонов вторым выражением (т. е. он не будет напечатан через секундураз командой по умолчанию p
, которая действует, когда не используется sed -n
).
Строка, начинающаяся только с #
, будет игнорироваться первым выражением, но удаляться вторым выражением.
Любая другая строка будет напечатана командой по умолчанию p
.
Чтобы разрешить пробелы перед #
(а также удалить эти строки):
sed -i -e '/^[[:blank:]]*#!/p' -e '/^[[:blank:]]*#/d' file
Выражение [[:blank:]]
будет соответствовать пробелу или табуляции. персонаж.
Как упомянул Стефан, это комментарии, изменение p
на b
в первом выражении позволит сценарию sed
продолжить работу со следующей строкой ввода без учета второе выражение, если первое выражение совпадает. Команда b
переходит к предопределенной метке или к концу сценария sed
, если метка не указана. Это будет оптимизация.
Для "и"двух адресов нужна группа команд ({...;}
):
sed '/^[[:space:]]*#/{/^#!/!d;}' < file
С GNU sed
, вы можете использовать -i
вместо inplace, замените [[:space:]]
на \s
(при условии, что это последняя версия) и опустите ;
:
sed -i '/^\s*#/{/^#!/!d}' file
Вы можете вложить несколько, но имейте в виду, что при переносе у вас не может быть ничего после }
. Таким образом, для A и B, а не C и не Dэто будет:
sed '/A/{/B/{/C/!{/D/!d;}' -e '}' -e '}' < file
Или:
sed '
/A/{
/B/{
/C/!{
/D/!d
}
}
}' < file
Вывод: удалить все комментарии, не относящиеся к шебангу.
sed -e '1{/^\s*\#/{/^\#!/!d}}' -e '1!{/^\s*\#/d}' file
Ваша команда изменена для использования одинарных кавычек (без двойных \\
):
sed '/^\(\s\)*\#/d'
будет работать почти правильно, просто добавив деталь, что (после #) должно быть что-то, что не является звездочкой [^!]
sed '/^\(\s\)*\#[^!]/d'. Но это не удастся с пустой строкой после символа комментария (
#`).
Для этого нужно утверждать, что линия закончилась ($
).
Для этого нам потребуется использование расширенного синтаксиса ([^!]|$)
:
sed -E '/^\s*\#([^!]|$)/d'
Или, более переносимого:
sed -E '/^[ \t]*\#([^!]|$)/d'
Однако для сценария только первая строка, которая начните как #!
имеет значение.
Все остальные строки, начинающиеся с необязательного пробела и #, являются комментариями:
sed -e '1{/^#!/!d}' -e '1!{/^[ \t]*#/d}' file
Что означает:
Первая -e
, если первая строка начинается с комментария (пробел и #), но не начинается точно с участием #! он стирается.
Вторая -e
другие строки (
1!
), начинающиеся с необязательного пробела и #, удаляются.
Используйте негативный взгляд -вперед в Perl:
perl -ne 'print unless /#(?!!)/'
Это удаляет строки, содержащие #
, за которыми не следует !
. Если вы хотите сопоставить #только в начале строки, возможно, с предшествующим пробелом, используйте
perl -ne 'print unless /^\s*#(?!!)/'
sed -i -e '/^\s*#\([^!]\|$\)/d'
Где:
^
начало строки \s*
ноль или более пробелов #
одна решетка \([^!]\|$\)
с последующим символом, который не является !
или конец строки. Мне помогло:
$ cat test.sed
# delete this one
#! don't delete this one
# delete this too
Команда:
$ sed -i -e '/^[[:space:]]*#[^!].*/d' test.sed
Результат:
$ cat test.sed
#! don't delete this one
Что ж, эта команда не удалит строк только с одиноким #
. Итак, я сохраняю этот ответ, чтобы показать, почему вам нужно ссылаться на другие решения.
Предположительно, вы хотите удалить все строки комментариев, (начинающиеся с любого количества пробелов, за которыми следует #
), кроме челки -, которая должна находиться в первой строке для обнаружения..
Вы можете использовать ту же самую команду подстановки, если вы ограничите ее рассмотрением только всего, начиная со строки 2; в нотации sed
это диапазон 2,$
. Вы ограничиваете команды sed
диапазоном, добавляя к ним префикс этого диапазона :
sed "2,${/^\(\\s\)*\\#/d}"
или с кавычками оболочки, которые позволяют использовать более читаемые обратные пробелы и без ненужной группировки регулярных выражений:
sed -e '2,${/^\s*#/d}'
Вход:
#!/path/to/a/shebang-command
# This is the first comment
command 1
# another comment
command 2
# Final comment
Выход:
#!/path/to/a/shebang-command
command 1
command 2