Самый быстрый способ определить наличие шебанга

Самый быстрый способ определить наличие шебанга

Если у меня есть файл с

#!/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

Связанный контент