这个问题是Shell POSIX OpenSSL文件解密脚本的第二个后续问题。
第一次跟进是:外壳POSIX OpenSSL文件解密脚本跟踪#1。
这两种方法都证明非常有用,并加快了我的shell脚本学习曲线。
你也许应该看看他们,但由于我做了这么多的改变,我觉得没有必要。
欢迎所有评论,如有小问题,请随时发表评论。
#!/bin/sh
###############################################################################
## OpenSSL file decryption POSIX shell script ##
## revision: 0.9 ##
## GitHub: https://git.io/fxslm ##
###############################################################################
# shellcheck disable=SC2016
# disable shellcheck information SC2016 globally for the script
# link to wiki: https://github.com/koalaman/shellcheck/wiki/SC2016
# reason: the script's main parts use constructs like that
# treat unset variables as an error when substituting
set -o nounset
# pipe will be considered successful only if all the commands involved are executed without errors
# ERROR: Illegal option -o pipefail. This likely works in Bash and alike only.
#set -o pipefail
#------------------------------------------------------------------------------
print_error_and_exit()
# expected arguments:
# $1 = exit code
# $2 = error origin (usually function name)
# $3 = error message
{
# redirect all output of this function to standard error stream
exec 1>&2
# check if exactly 3 arguments have been passed
# if not, print out an internal error without colors
if [ "${#}" -ne 3 ]
then
printf 'print_error_and_exit internal error\n\n\tWrong number of arguments has been passed: %b!\n\tExpected the following 3:\n\t\t$1 - exit code\n\t\t$2 - error origin\n\t\t$3 - error message\n\nexit code = 1\n' "${#}"
exit 1
fi
# check if the first argument is a number
# if not, print out an internal error without colors
if ! [ "${1}" -eq "${1}" ] 2> /dev/null
then
printf 'print_error_and_exit internal error\n\n\tThe first argument is not a number: %b!\n\tExpected an exit code from the script.\n\nexit code = 1\n' "${1}"
exit 1
fi
# check if we have color support
if command -v tput > /dev/null 2>&1 && tput setaf 1 > /dev/null 2>&1
then
# color definitions
readonly bold=$(tput bold)
readonly red=$(tput setaf 1)
readonly yellow=$(tput setaf 3)
readonly nocolor=$(tput sgr0)
# here we do have color support, so we highlight the error origin and the exit code
printf '%b%b\n\n\t%b%b%b\n\nexit code = %b%b\n' \
"${bold}${yellow}" "${2}" "${nocolor}" \
"${3}" \
"${bold}${red}" "${1}" "${nocolor}"
exit "${1}"
else
# here we do not have color support
printf '%b\n\n\t%b\n\nexit code = %b\n' \
"${2}" "${3}" "${1}"
exit "${1}"
fi
}
#------------------------------------------------------------------------------
# in this function, the SC2120 warning is irrelevant and safe to ignore
# link to wiki: https://github.com/koalaman/shellcheck/wiki/SC2120
# shellcheck disable=SC2120
am_i_root()
# expected arguments: none
{
# check if no argument has been passed
[ "${#}" -eq 0 ] || print_error_and_exit 1 "am_i_root" "Some arguments have been passed to the function!\\n\\tNo arguments expected.\\n\\tPassed: ${*}"
# check if the user is root
# this will return an exit code of the command itself directly
[ "$(id -u)" -eq 0 ]
}
# check if the user had by any chance run the script with root privileges
# if you need to run it as root, feel free to comment out the line below
# in this function call, the SC2119 information is irrelevant and safe to ignore
# link to wiki: https://github.com/koalaman/shellcheck/wiki/SC2119
# shellcheck disable=SC2119
am_i_root && print_error_and_exit 1 "am_i_root" "Running this script with root privileges is discouraged!\\n\\tQuiting to shell."
#------------------------------------------------------------------------------
check_for_prerequisite()
# expected arguments:
# $1 = command / program name
{
# check if exactly one argument has been passed
[ "${#}" -eq 1 ] || print_error_and_exit 1 "check_for_prerequisite" "Exactly one argument has not been passed to the function!\\n\\tOne command to test expected.\\n\\tPassed: ${*}"
# check if the argument is a program which is installed
command -v "${1}" > /dev/null 2>&1 || print_error_and_exit 1 "check_for_prerequisite" "This script requires '${1}' but it is not installed or available on this system!\\n\\tPlease install the corresponding package manually."
}
check_for_prerequisite 'openssl'
check_for_prerequisite 'pv'
check_for_prerequisite 'file'
check_for_prerequisite 'grep'
#------------------------------------------------------------------------------
is_number()
# expected arguments:
# $1 = variable or literal
{
# check if exactly one argument has been passed
[ "${#}" -eq 1 ] || print_error_and_exit 1 "is_number" "Exactly one argument has not been passed to the function!\\n\\tOne variable or literal to test expected.\\n\\tPassed: ${*}"
# check if the argument is an integer number
# this will return an exit code of the command itself directly
[ "${1}" -eq "${1}" ] 2> /dev/null
}
#------------------------------------------------------------------------------
print_usage_and_exit()
{
# check if exactly one argument has been passed
[ "${#}" -eq 1 ] || print_error_and_exit 1 "print_usage_and_exit" "Exactly one argument has not been passed to the function!\\n\\tPassed: ${*}"
# check if the argument is a number
is_number "${1}" || print_error_and_exit 1 "print_usage_and_exit" "The argument is not a number!\\n\\Expected an exit code from the script.\\n\\tPassed: ${1}"
# in case of non-zero exit code given, redirect all output to stderr
[ "${1}" -ne 0 ] && exec 1>&2
echo "Usage: ${0} [-o directory] file"
echo
echo " -o directory: Write the output file into the given directory;"
echo " Optional and must be given before the file."
echo
echo " file: Regular file to decrypt."
exit "${1}"
}
#------------------------------------------------------------------------------
given_output_directory=
while getopts ":ho:" option
do
case "${option}" in
o)
given_output_directory="${OPTARG}"
;;
h)
print_usage_and_exit 0
;;
*)
print_usage_and_exit 1
;;
esac
done
shift $(( OPTIND - 1 ))
#------------------------------------------------------------------------------
[ "${#}" -eq 0 ] && print_usage_and_exit 1
#------------------------------------------------------------------------------
[ "${#}" -gt 1 ] && print_error_and_exit 1 '[ "${#}" -gt 1 ]' "You have passed ${#} arguments to the script!\\n\\tOnly one file expected.\\n\\tPassed: ${*}"
#------------------------------------------------------------------------------
[ -f "${1}" ] || print_error_and_exit 1 '[ -f "${1}" ]' "The given argument is not an existing regular file!\\n\\tPassed: ${1}"
#------------------------------------------------------------------------------
input_file="${1}"
[ -r "${input_file}" ] || print_error_and_exit 1 '[ -r "${input_file}" ]' "Input file is not readable by you!\\n\\tPassed: ${input_file}"
#------------------------------------------------------------------------------
is_file_encrypted_using_openssl()
{
# check if exactly one argument has been passed
[ "${#}" -eq 1 ] || print_error_and_exit 1 "is_file_encrypted_using_openssl" "Exactly one argument has not been passed to the function!\\n\\tPassed: ${*}"
# check if the argument is a file
[ -f "${1}" ] || print_error_and_exit 1 "is_file_encrypted_using_openssl" "The provided argument is not a regular file!\\n\\tPassed: ${1}"
# check if the provided file has been encrypted using openssl
# this will return an exit code of the command itself directly
file "${1}" | grep --ignore-case 'openssl' > /dev/null 2>&1
}
is_file_encrypted_using_openssl "${input_file}" || print_error_and_exit 1 "is_file_encrypted_using_openssl" "Input file does not seem to have been encrypted using OpenSSL!\\n\\tPassed: ${input_file}"
#------------------------------------------------------------------------------
# parameter substitution with - modifier will cause the output_directory
# variable to to get dirname ... in case given_output_directory is empty
output_directory="${given_output_directory:-$(dirname "${input_file}")}"
[ -d "${output_directory}" ] || print_error_and_exit 1 '[ -d "${output_directory}" ]' "Destination:\\n\\t\\t${output_directory}\\n\\tis not a directory!"
[ -w "${output_directory}" ] || print_error_and_exit 1 '[ -w "${output_directory}" ]' "Destination directory:\\n\\t\\t${output_directory}\\n\\tis not writable by you!"
#------------------------------------------------------------------------------
filename_extracted_from_path=$(basename "${input_file}")
filename_without_enc_extension="${filename_extracted_from_path%.enc}"
if [ "${filename_extracted_from_path}" = "${filename_without_enc_extension}" ]
then
# the file has a different than .enc extension or no extension at all
# what we do now, is that we append .dec extention to the file name
output_file="${output_directory}/${filename_extracted_from_path}.dec"
else
# the file has the .enc extension
# what we do now, is that we use the file name without .enc extension
output_file="${output_directory}/${filename_without_enc_extension}"
fi
#------------------------------------------------------------------------------
# -e FILE: True if file exists. Any type of file!
[ -e "${output_file}" ] && print_error_and_exit 1 '[ -e "${output_file}" ]' "Destination file:\\n\\t\\t${output_file}\\n\\talready exists!"
#------------------------------------------------------------------------------
# here comes the core part - decryption of the given file
if ! pv --wait "${input_file}" | openssl enc -aes-256-cbc -md sha256 -salt -out "${output_file}" -d 2> /dev/null
then
[ -f "${output_file}" ] && rm "${output_file}"
echo
print_error_and_exit 1 'pv --wait "${input_file}" | openssl enc -aes-256-cbc -md sha256 -salt -out "${output_file}" -d' "Decryption failed!"
else
echo
echo "Decryption successful."
# scripts exit with 0 exit code by default, in case of success,
# so this just more explicit for readers
exit 0
fi该项目被上传到GitHub:https://git.io/fxslm上。
但不是这次修改。我将从答案中学习,然后第一次发布。
我现在意识到我应该检查一下空闲空间,所以我添加了以下内容:
file_size=$(du --bytes "${input_file}" | awk '{ print $1 }')
free_space=$(df "${output_directory}" | tail --lines=1 | awk '{ print $4 }')
[ "${free_space}" -gt "${file_size}" ] || print_error_and_exit 1 '[ "${free_space}" -gt "${file_size}" ]' "There is not enough free space in the destination directory!\\n\\t\\tFile size: ${file_size}\\n\\t\\tFree space: ${free_space}"通过将-B1添加到df中,我修正了以字节为单位获取空闲空间的问题:
free_space=$(df -B1 "${output_directory}" | tail --lines=1 | awk '{ print $4 }')发布于 2018-10-15 06:38:58
这个脚本是POSIX和非POSIX奇怪的混合体.一方面,openssl绝对不是POSIX,但是它是非常重要的,因为这就是整个脚本的意义所在。另一方面,这里为POSIX实用程序使用的许多选项都不是标准的,pv对于脚本的操作也不是必不可少的。(这里有用吗?是。这里有必要吗?不是。)因此,如果要在任何有意义的意义上兼容POSIX+openssl:
-i而不是--ignore-case和grep。还有抑制输出的-q选项。du和df的默认512字节块输出和awk中的乘数乘以512,或者对1024字节块使用-k,然后乘以1024,而不是使用--byte。df和du不幸地坚持1024字节块,即使使用-P,也可以使用-kP (带有POSIX输出的1024字节)来实现可预测性。pv仅用于进度,则如果可用,则使用它,但如果没有,则使用cat。该脚本包含大量错误消息,但有一个严重的问题:您放弃openssl's错误,然后打印自己无用的错误消息。“解密失败!”?用户如何知道在没有openssl's错误输出的情况下为什么失败?如果发生错误,您应该保留openssl输出并打印(无论如何,我会从openssl打印所有内容,但如果您真的必须隐藏它,则只在成功的情况下隐藏它):
if ! openssl_out="$(pv --wait "${input_file}" | openssl enc -aes-256-cbc -md sha256 -salt -out "${output_file}" -d 2>&1)"
then
[ -f "${output_file}" ] && rm "${output_file}"
print_error_and_exit 1 'pv --wait "${input_file}" | openssl enc -aes-256-cbc -md sha256 -salt -out "${output_file}" -d' "Decryption failed!: ${openssl_out}"
...而且,对于输出消息,input_file和output_file (以及其他错误消息中的其他各种变量)也不会被扩展。这减少了这些标签的效用,因为用户不会看到实际运行的是什么。
# scripts exit with 0 exit code by default, in case of success,
# so this just more explicit for readers
exit 0不,他们没有。他们以执行的最后一个命令的退出状态退出,在这种情况下,这应该是echo "Decryption successful.",如果echo由于某种原因失败了,那么就会出现更大的问题。exit 0作为shell脚本中的最后一个命令实际上是无用的,除非前面的命令需要返回一个非零的退出代码。
check_for_prerequisite可以遍历参数。它没有理由只接受一个论点。
if command -v tput > /dev/null 2>&1 && tput setaf 1 > /dev/null 2>&1
then
# color definitions
readonly bold=$(tput bold)
...
exit "${1}"
else
# here we do not have color support
printf '%b\n\n\t%b\n\nexit code = %b\n' \
"${2}" "${3}" "${1}"
exit "${1}"
fi将exit "${1}"移到if块之外。不管怎样,你都要去exit。
为什么要打印出口代码?无论如何,用户都可以很容易地访问它。
filename_extracted_from_path=$(basename "${input_file}")
filename_without_enc_extension="${filename_extracted_from_path%.enc}"您不需要引用变量赋值,但是如果要引用变量赋值,则必须保持一致,并引用命令替换。
https://codereview.stackexchange.com/questions/205348
复制相似问题