Last active
July 2, 2021 07:38
-
-
Save abcarroll/47bc02fcd7573282f197e3782c6cbbeb to your computer and use it in GitHub Desktop.
hash-file
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/php | |
<?php declare(strict_types=1); | |
/** | |
* Simple hashing utility using PHP | |
* | |
* @author A.B. Carroll III <[email protected]> | |
* @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