Если у меня есть файл с
#!/usr/bin/env foobar
какой самый быстрый/лучший способ определить, есть ли у этого файла hashbang? Я слышал, что можно просто прочитать первые 2 байта? Как?
решение1
С zsh
:
if LC_ALL=C read -u0 -k2 shebang < file && [ "$shebang" = '#!' ]; then
echo has shebang
fi
То же самое с ksh93
или bash
:
if IFS= LC_ALL=C read -rN2 shebang < file && [ "$shebang" = '#!' ]; then
echo has shebang
fi
хотя bash
даст ложные срабатывания для файлов, начинающихся с NUL, за которыми следует #!
и будет читатьсявсеначальные байты NUL, например, будут считывать файл размером в один тебибайт, созданный truncate -s1T file
полностью по 2 байта за раз.
Поэтому с bash
, было бы лучше использовать:
IFS= LC_ALL=C read -rn2 -d '' shebang
Это прочитановплоть до2 байта записи, разделенной символом NUL.
Они не разветвляют процессы и не выполняют дополнительные команды, как read
, [
и echo
все команды являются встроенными.
С помощью POSIX вы можете сделать следующее:
if IFS= read -r line < file; then
case $line in
("#!"*) echo has shebang
esac
fi
Он строже в том, что также требует полной строки. По крайней мере, в Linux для допустимого shebang-а новая строка не требуется.
Итак, вы можете сделать:
line=
IFS= read -r line < file
case $line in
("#!"*) echo has shebang
esac
Он немного менее эффективен, так как потенциально может считывать больше байтов, некоторые оболочки по одному байту за раз. С нашим разреженным файлом размером 1TiB это заняло бы много времени в большинстве оболочек (и потенциально использовало бы много памяти).
В оболочках, отличных от zsh
, также могут быть ложные срабатывания для файлов, начинающихся с NUL, за которыми следует #!
.
С yash
оболочкой произойдет сбой, если шебанг содержит последовательности байтов, которые не образуют допустимые символы в текущей локали (даже произойдет сбой (по крайней мере, в версии 2.39 и старше), если шебанг содержит не-ASCII символы в локали C, даже несмотря на то, что локаль C подразумевает, что все символы являются отдельными байтами, а все байтовые значения образуют допустимые --даже если не обязательно определенные -- символы).
Если вы хотите найти все файлы, содержимое которых начинается с #!
, вы можете сделать следующее:
PERLIO=raw find . -type f -size +4c -exec perl -T -ne '
BEGIN{$/=\2} print "$ARGV\n" if $_ eq "#!"; close ARGV' {} +
Мы рассматриваем только файлы размером не менее 5 байт ( #!/x\n
минимальный реалистичный размер).
- с помощью
-exec perl... {} +
мы передаем как можно больше путей к файлам,perl
чтобы выполнить как можно меньше вызовов -T
это обойтичто ограничениеperl -n
а также означает, что он не будет работать для файлов, имена которых заканчиваются символами пробела ASCII или|
.PERLIO=raw
заставляетperl
использоватьread()
системные вызовы напрямую, без какого-либо уровня буферизации ввода-вывода (также влияет на печать имен файлов), поэтому он будет выполнять чтения размером 2.$/ = \2
когда разделитель записей задан как ссылка на число, это приводит к тому, что записи становятся записями фиксированной длины.close ARGV
пропускает оставшуюся часть текущего файла после прочтения первой записи.
решение2
Вы можете определить свои собственные «магические шаблоны» /etc/magic
и использовать их file
для тестирования:
$ sudo vi /etc/magic
$ cat /etc/magic
# Magic local data for file(1) command.
# Insert here your local magic data. Format is described in magic(5).
0 byte 0x2123 shebang is present
$ cat /tmp/hole2.sh #To prove [1] order of hex [2] 2nd line ignored
!#/bin/bash
#!/bin/bash
$ cat /tmp/hole.sh
#!/bin/bash
$ file /tmp/hole2.sh
/tmp/hole2.sh: ASCII text
$ file /tmp/hole.sh
/tmp/hole.sh: shebang is present
$ file -b /tmp/hole.sh #omit filename
shebang is present
0x2123
представляет собой шестнадцатеричное значение «#!» в обратном порядке:
$ ascii '#' | head -n1
ASCII 2/3 is decimal 035, hex 23, octal 043, bits 00100011: prints as `#'
$ ascii '!' | head -n1
ASCII 2/1 is decimal 033, hex 21, octal 041, bits 00100001: prints as `!'
По желанию можно поставить:
0 string \#\! shebang is present
ссылка: man 5 magic
, man 1 file
, man 1posix file
решение3
Этого должно хватить:
if [ "`head -c 2 infile`" = "#!" ]; then
echo "Hashbang present"
else
echo "no Hashbang present"
fi
решение4
использовать grep
в однострочном решении
if head -1 file | grep "^#\!" > /dev/null;then echo "true"; fi