Skip to content

Instantly share code, notes, and snippets.

@abcarroll
Last active July 2, 2021 07:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save abcarroll/47bc02fcd7573282f197e3782c6cbbeb to your computer and use it in GitHub Desktop.
Save abcarroll/47bc02fcd7573282f197e3782c6cbbeb to your computer and use it in GitHub Desktop.
hash-file
#!/usr/bin/php
<?php declare(strict_types=1);
/**
* Simple hashing utility using PHP
*
* @author A.B. Carroll III <ben@hl9.net>
* @license MIT
*
* @see https://gist.github.com/abcarroll/47bc02fcd7573282f197e3782c6cbbeb
*/
/**
* Argument Handling
*/
$args = array_slice($argv, 1);
if (count($args) === 0) {
stderr("#! FATAL: No filename(s) given.\n");
fatal();
}
if (count($args) === 1 && ($args[0] === '-h' || $args[0] === '--help' || $args[0] === '-help')) {
print_usage();
return 1;
}
$showHeader = false;
$errors = [];
$files = [];
if (true === in_array('::', $args, true)) {
$algos = [];
$validAlgos = array_merge(hash_algos(), [
'+filename', '+filepath', '+realpath', '+fullpath',
'+dirpath', '+dirname',
'+filesize'
]);
$isInFilePart = false;
foreach ($args as $arg) {
if (true === $isInFilePart) {
$files[] = $arg;
} else {
if ($arg === '::') {
$isInFilePart = true;
} elseif ($arg === '--all') {
$algos = array_merge($algos, hash_algos());
} elseif ($arg === '--header') {
$showHeader = true;
} else {
if (false === in_array($arg, $validAlgos, true)) {
$errors[] = "The algorithm '$arg' is invalid";
} else {
$algos[] = $arg;
}
}
}
}
} else {
$files = $args;
$algos = ['+filepath', '+filesize', 'crc32c', 'md5', 'sha1', 'sha256'];
}
if (count($errors) !== 0) {
handleFatalErrorArray($errors);
fatal();
}
foreach ($files as $file) {
if (false === is_file($file)) {
$errors[] = "Path '$file' is not a valid file";
} elseif (false === is_readable($file)) {
$errors[] = "Path '$file' is not readable";
}
}
if (count($errors) !== 0) {
handleFatalErrorArray($errors);
fatal();
}
$lastAlgoKey = array_key_last($algos);
if ($showHeader) {
echo "# ";
foreach ($algos as $algoKey => $algo) {
if (substr($algo, 0, 1) === '+') {
echo substr($algo, 1);
} else {
echo $algo;
}
if ($lastAlgoKey !== $algoKey) {
echo "\t";
} else {
echo "\n";
}
}
}
foreach ($files as $file) {
foreach ($algos as $algoKey => $algo) {
echo match ($algo) {
'+filepath' => $file,
'+realpath', '+fullpath' => realpath($file),
'+dirpath' => dirname(realpath($file)) . '/',
'+dirname' => dirname($file) . '/',
'+filename' => basename($file),
'+filesize' => filesize($file),
default => hash_file($algo, $file)
};
if ($lastAlgoKey !== $algoKey) {
echo "\t";
} else {
echo "\n";
}
}
}
return 0;
function get_usage_body(): string
{
$exeName = $_SERVER['argv'][0];
$body = "
NAME
hash-file - Hash files and print them to the standard output
SYNOPSIS
cksum-all [files]...
cksum-all [columns]... :: [files]...
SIMPLE USAGE
In the simplest usage, invoke with one or more file(s) as arguments.
This will use the default options:
+filename +filesize crc32c md5 sha1 sha256
ADVANCED USAGE
In the more advanced usage, you may specify which algorithms are printed
and in what order they are printed, in addition to optionally displaying a
header, filepath, fullpath, filesize, and more.
Invoke followed by one or more options, followed by '::', then followed by
one or more file(s) as arguments. For example, leveraging shell expansion,
the following example ...
${exeName} filepath sha256 :: *.jpg
... will display the filepath as given in the argument, followed by a tab,
followed by the sha256 in hexadecimal, followed by a newline for each file
encountered.
ADVANCED USAGE OPTIONS
+filesize File Size (does not induce a slower stat() call)
+filepath The file path as given in the argument
+filename The basename of the file path as given in the argument
+realpath The real path of the file argument given (resolves symlinks)
In addition, the following algorithms are supported:
";
$body = trim($body) . "\n\n";
$algos = hash_algos();
foreach ($algos as $x => $algo) {
if (($x % 4) === 0) {
$body .= " ";
}
$body .= str_pad($algo, 20, " ", STR_PAD_RIGHT);
if (($x % 4) === 3) {
$body .= "\n";
}
}
$body .= "\n\n";
return $body;
}
function print_usage(): void
{
echo get_usage_body();
}
function stderr(string $msg): void
{
fwrite(STDERR, $msg);
}
function fatal($exitCode = 1)
{
stderr("\nTry `" . $_SERVER['argv'][0] . " --help` for manual.\n");
exit($exitCode);
}
function handleFatalErrorArray(array $errors): void
{
fwrite(STDERR, "#! FATAL: " . count($errors) . " errors occurred:\n");
foreach ($errors as $e) {
fwrite(STDERR, "#> $e\n");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment