Isso deve ser feito:
if [ "'head -c 2 infile'" = "#!" ]; then
echo "Hashbang present"
else
echo "no Hashbang present"
fi
Se eu tiver um arquivo com
#!/usr/bin/env foobar
qual é a maneira mais rápida / melhor para determinar se este arquivo tem um hashbang? Eu ouvi que você pode apenas ler os primeiros 2 bytes? Como?
Isso deve ser feito:
if [ "'head -c 2 infile'" = "#!" ]; then
echo "Hashbang present"
else
echo "no Hashbang present"
fi
com zsh
:
if LC_ALL=C read -u0 -k2 shebang < file && [ "$shebang" = '#!' ]; then
echo has shebang
fi
Mesmo com ksh93
ou bash
:
if IFS= LC_ALL=C read -rN2 shebang < file && [ "$shebang" = '#!' ]; then
echo has shebang
fi
embora bash
daria falsos positivos para arquivos que iniciam com NULs seguidos por #!
e leria todos os bytes NUL principais para ler um arquivo tebibyte criado com truncate -s1T file
totalmente 2 bytes de cada vez, por exemplo.
Portanto, com bash
, seria melhor usar:
IFS= LC_ALL=C read -rn2 -d '' shebang
Isso é lido até 2 bytes de um registro delimitado por NUL.
Eles não bifurcam processos nem executam comandos extras, pois os comandos read
, [
e echo
são todos internos.
POSIXly, você pode fazer:
if IFS= read -r line < file; then
case $line in
("#!"*) echo has shebang
esac
fi
É mais estrito que também requer uma linha completa. Pelo menos no Linux, a nova linha não é necessária para um shebang válido.
Então você poderia fazer:
line=
IFS= read -r line < file
case $line in
("#!"*) echo has shebang
esac
É um pouco menos eficiente, pois potencialmente lê mais bytes, com alguns shells um byte de cada vez. Com o nosso arquivo esparso de 1TiB, isso levaria muito tempo na maioria dos shells (e potencialmente usaria muita memória).
Com shells diferentes de zsh
, ele também pode fornecer falsos positivos para arquivos que começam com NULs seguidos por #!
.
Com o shell yash
, ele falharia se o shebang contivesse seqüências de bytes que não formam caracteres válidos no código de idioma atual (falharia mesmo (pelo menos com 2.39 e mais) se o shebang contivesse não-ASCII caracteres na localidade C, mesmo que a localidade C deva ser aquela em que todos os caracteres são bytes únicos e todos os valores de bytes formam caracteres válidos - mesmo que não necessariamente definidos--)
Se você quiser encontrar todos os arquivos cujo conteúdo começa com #!
, você pode fazer:
PERLIO=raw find . -type f -size +4c -exec perl -T -ne '
BEGIN{$/=} print "$ARGV\n" if $_ eq "#!"; close ARGV' {} +
Estamos considerando apenas arquivos com pelo menos 5 bytes de tamanho ( #!/x\n
do mínimo realista).
-exec perl... {} +
, passamos o máximo possível de caminhos de arquivo para perl
, por isso, execute o menor número possível de invocações -T
é para contornar essa limitação de perl -n
e também significa que não funcionará para arquivos cujo nome termine em caracteres de espaçamento ASCII ou |
. PERLIO=raw
faz com que perl
use read()
chamadas de sistema diretamente sem qualquer camada de buffer de E / S (afeta também a impressão de nomes de arquivos), portanto, fará leituras de tamanho 2. $/ =
quando o separador de registro é definido como uma referência a um número, faz com que os registros sejam de comprimento fixo. close ARGV
pula o restante do arquivo atual depois que lemos o primeiro registro. use grep
em uma solução de uma linha
if head -1 file | grep "^#\!" > /dev/null;then echo "true"; fi
O jejum pode ou não ser o melhor, dependendo dos seus sentimentos em compilar um monte de C (ou talvez algum assembly para tirar toda aquela sobrecarga de C do caminho. e toda aquela tediosa verificação de erro, sheesh ...)
#include <sys/types.h>
#include <err.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>
#include <unistd.h>
int Flag_Quiet; /* -q */
void emit_help(void);
int main(int argc, char *argv[])
{
int ch;
char two[2];
ssize_t amount;
while ((ch = getopt(argc, argv, "h?q")) != -1) {
switch (ch) {
case 'q':
Flag_Quiet = 1;
break;
case 'h':
case '?':
default:
emit_help();
/* NOTREACHED */
}
}
argc -= optind;
argv += optind;
if (argc < 1)
emit_help();
if ((ch = open(*argv, O_RDONLY)) == -1)
err(EX_IOERR, "could not open '%s'", *argv);
amount = read(ch, two, 2);
if (amount == -1) {
err(EX_IOERR, "read failed on '%s'", *argv);
} else if (amount == 0) {
err(EX_IOERR, "EOF on read of '%s'", *argv);
} else if (amount == 2) {
if (two[0] == '#' && two[1] == '!') {
amount = 0;
} else {
amount = 1;
}
} else {
errx(EX_IOERR, "could not read two bytes from '%s'", *argv);
}
if (!Flag_Quiet) {
printf("%s\n", amount ? "no" : "yes");
}
exit(amount);
}
void emit_help(void)
{
fprintf(stderr, "Usage: hazshebang [-q] file\n");
exit(EX_USAGE);
}
Isso exigirá alguns ajustes se você quiser um "não" no padrão ao lado de uma das (muitas!) err
saídas do acima. Provavelmente, é melhor verificar a palavra de status de saída.
O modo de shell mais lento com head -c 2 file
falha em um teste rápido de portabilidade para o OpenBSD.
$ head -c 2 /etc/passwd
head: unknown option -- c
usage: head [-count | -n count] [file ...]
$
Você pode definir seus próprios "padrões mágicos" em /etc/magic
e usar file
para testar:
$ 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
é hexadecimal de '#!' na ordem inversa:
$ 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 '!'
Opcionalmente, você pode colocar:
0 string \#\! shebang is present
ref: man 5 magic
, man 1 file
, man 1posix file
Tags executable shebang shell exec