_ARCHIVE_PATTERN="_[0-9]{12}-[[:alnum:]]{40}.t[xg]z" _ARCHIVE_DATE() { date +%Y%m%d%H%M } # archive file with maximum compression and checksum. # usage: archive [FILES] archive() { local targets=("${@}") # target file(s). local count=0 # processed count. local total=${#} # total to process. local date=$(_ARCHIVE_DATE) # date stamp. # set dafult value to target all supported archives. if [[ "${targets}" = "" ]]; then targets=(*) total=${#targets[@]} fi # iterate each target. for target in "${targets[@]}"; do # increment counter. ((count++)) # status info. local status="[${count}/${total}] ${target}" echo "${status}" # create archive. tar -c "${target}" | pv -s $(du -sb "${target}" | awk '{print $1}') | xz -9e > "${target%/*}".txz # append hash to target name. mv "${target%/*}".txz "${target%/*}"_${date}-$(pv "${target%/*}".txz | sha1sum | cut -d\ -f1).txz # report success. echo -e "${color_default}${status}: Done.${color_default}" done } # archive file with minimal compression and checksum. # usage: archive_fast [FILES] archive_fast() { local targets=("${@}") # target file(s). local count=0 # processed count. local total=${#} # total to process. local date=$(_ARCHIVE_DATE) # date stamp. # set dafult value to target all supported archives. if [[ "${targets}" = "" ]]; then targets=(*) total=${#targets[@]} fi # iterate each target. for target in "${targets[@]}"; do # increment counter. ((count++)) # status info. local status="[${count}/${total}] ${target}" echo "${status}" # create archive. tar -c "${target}" | pv -s $(du -sb "${target}" | awk '{print $1}') | gzip -1 > "${target%/*}".tgz # append hash to target name. mv "${target%/*}".tgz "${target%/*}"_${date}-$(pv "${target%/*}".tgz | sha1sum | cut -d\ -f1).tgz # report success. echo -e "${color_default}${status}: Done.${color_default}" done } # check archive hashes. # usage: archive_check [FILES] archive_check() { local targets=("${@}") # target file(s). local total=${#} # total to process. local count=0 # processed count. local failed=0 # total failed checks. # set dafult value to target all supported archives. if [[ "${targets}" = "" ]]; then targets=($(ls | grep -E ${_ARCHIVE_PATTERN})) total=${#targets[@]} fi # iterate each target. for target in "${targets[@]}"; do # process only files. [[ -f "${target}" ]] || continue # increment counter. ((count++)) # status info. local status="[${count}/${total}] ${target}" # extract hash from name. local saved="${target##*-}" saved="${saved%%.*}" # calculate actual hash. local actual=$(pv "${target}" | sha1sum | cut -d\ -f1) # compare hashes, show error on mismatch. if [[ "${actual}" = "${saved}" ]]; then echo -e "${color_default}${status}: Validation OK.${color_default}" else echo -e "${color_bred}${status}: Validation failed.${color_default}" ((failed++)) fi done # report result. if [[ ${count} -gt 1 ]] || [[ "${*}" = "" ]]; then if [[ ${failed} -gt 0 ]]; then echo -e "${color_bred}Items failed to validate: ${failed}.${color_default}" else echo -e "${color_green}All successful.${color_default}" fi fi } # extract previously created archive with checksum validation. # usage: unarchive [FILES] unarchive() { local targets=("${@}") # target file(s). local count=0 # processed count. local total=${#} # total to process. # set dafult value to target all supported archives. if [[ "${targets}" = "" ]]; then targets=($(ls | grep -E ${_ARCHIVE_PATTERN})) total=${#targets[@]} fi # iterate each target. for target in "${targets[@]}"; do # increment counter. ((count++)) # status info. local status="[${count}/${total}] ${target}" # extract hash from name. local saved="${target##*-}" saved="${saved%%.*}" # calculate actual hash. local actual=$(pv "${target}" | sha1sum | cut -d\ -f1) # extract if hash matched or show error if not. if [[ "${saved}" = "${actual}" ]]; then echo "${status}: Validation OK." # figure out the compression tool. local compressor case "${target##*.}" in "txz") compressor="xz -d" ;; "tgz") compressor="gzip -d" ;; esac # extract. pv "${target}" | ${compressor} | tar -xf - else # report validation error & exit. echo -e "${color_bred}${status}: Validation failed.${color_default}" return 1 fi # report extraction complete. echo -e "${color_default}${status}: Done.${color_default}" done } # rename archive. if no name specified, it simplifies archive's name. # usage: archive_name [ARCHIVE] [NAME] archive_name() { local targets="${1}" # target archive(s). local name="${2}" # new name. local total=1 # total targets to process. local count=0 # processed targets counter. # set dafult value to target all supported archives. if [[ "${targets}" = "" ]]; then targets=($(ls | grep -E ${_ARCHIVE_PATTERN})) total=${#targets[@]} fi # iterate each target. for target in "${targets[@]}"; do # only work with targets. [[ -f "${target}" ]] || continue # iterate counter. ((count++)) # simplify name by default. if [[ "${name}" = "" || ${count} -gt 1 ]]; then name="${target%_*}" name="$(parse_alnum ${name})" fi # remove old name. local data="${target##*_}" local new_name="${name}_${data}" # prepare status. local status="[${count}/${total}] ${target} -> ${new_name}" # check for the same name. if [[ "${target}" = "${new_name}" ]]; then echo -e "${color_green}${status}: No change.${color_default}" continue fi # check for existing target. if [[ -f "${new_name}" ]]; then echo -e "${color_bred}${status}: Already exists.${color_default}" return 1 fi # rename. mv -- "${target}" "${new_name}" && echo -e "${color_default}${status}${color_default}" || echo -e "${color_bred}${status}: Error.${color_default}" done } # convert an old archive to a new format. TODO: remove me after some time when there won't be any old archives. archive_convert() { local old_format="_[[:alnum:]]{40}.tar.[xg]z" local targets=($(ls | grep -E ${old_format})) # add timestamp. for target in "${targets[@]}"; do local stamp=$(stat --format '%w' -- "${target}" | sed -e 's/\..*//' -e 's/:..$//' -e 's/-//g' -e 's/://' -e 's/\ //') local name="${target%_*}" local old_data="${target##*_}" local new_name="${name}_${stamp}-${old_data}" echo "${target} -> ${new_name}" mv "${target}" "${new_name}" done # convert tar.xz and tar.gz to .tgz and .txz. old_format="_[0-9]{12}-[[:alnum:]]{40}.tar.[xg]z" targets=($(ls | grep -E ${old_format})) for target in "${targets[@]}"; do local compression="${target##*.}" local new_compression case "${compression}" in "gz") new_compression="tgz" ;; "xz") new_compression="txz" ;; esac local new_name="${target%.tar.*}".${new_compression} echo "${target} -> ${new_name}" mv -- "${target}" "${new_name}" done } # export everything, primarily for use with parallel.. export -f archive archive_fast archive_check unarchive archive_name _ARCHIVE_DATE export _ARCHIVE_PATTERN # autocomplete. _archive_name() { COMPREPLY=() local cur="${COMP_WORDS[COMP_CWORD]}" local prev="${COMP_WORDS[COMP_CWORD-1]}" local command="${COMP_WORDS[0]}" if [[ "${prev}" = "${command}" ]]; then COMPREPLY=( $(compgen -W "$(ls | grep -E ${_ARCHIVE_PATTERN})" -- ${cur}) ) return 0 else local name="${prev%_*}" COMPREPLY=( $(compgen -W "${name}" -- ${cur}) ) return 0 fi } _archive_grep() { _autocomplete_grep ${_ARCHIVE_PATTERN} } complete -F _archive_grep archive_check unarchive complete -F _archive_name archive_name