我该如何制作一个可以在sh上执行的自解压归档文件?我最接近的是:
sh
extract_archive () { printf '<archive_contents>' | tar -C "$extract_dir" -xvf - }
其中<archive_contents>包含一个包含空字符的tarball,%、'和\字符被转义并用单引号括起来。有没有更好的方法来做到这一点,以便不需要逃避?(请不要指向shar,makeself等。我想从头开始写。)
<archive_contents>
%
'
\
shar
makeself
ulydmbyx1#
另一种变体是使用标记结束shell脚本,使用sed剪切shell脚本本身。脚本selfextract.sh:
selfextract.sh
#!/bin/bash sed '0,/^#EOF#$/d' $0 | tar zx; exit 0 #EOF#
使用方法:
# create sfx cat selfextract.sh data.tar.gz >example_sfx.sh # unpack sfx bash example_sfx.sh
woobm2wo2#
由于shell脚本不是编译的,而是逐语句执行的,所以可以使用如下模式混合二进制和文本内容(未经测试):
#!/bin/sh sed -e '1,/^exit$/d' "$0" | tar -C "${1-.}" -zxvf - exit <binary tar gzipped content here>
您可以将这两行添加到几乎任何tar+gzip文件的顶部,使其具有自解压功能。要测试:
$ cat header.sh #!/bin/sh sed -e '1,/^exit$/d' "$0" | tar -C "${1-.}" -zxvf - exit $ tar -czf header.tgz header.sh $ cat header.sh header.tgz > header.tgz.sh $ sh header.tgz.sh header.sh
nnsrf1az3#
一些关于如何做到这一点的好文章可以在以下网站找到:
fslejnso4#
是的,你可以用xtar原生地完成它。1.构建xtar elf64 tar自解压头(你可以自由修改它以支持elf32,pe等可执行格式),它基于轻量级bsdtar untar和std elf lib.
xtar
cc contrib/xtar.c -o ./xtar
1.将xtar二进制文件复制到yourTar.xtar
yourTar.xtar
cp ./xtar yourTar.xtar
1.将yourTar.tar存档追加到yourTar.xtar的末尾
yourTar.tar
cat yourTar.tar >> yourTar.xtar chmod +x yourTar.xtar
piah890a5#
下面是一个dash脚本(应该可以在Linux和Mac OS-es上开箱即用),它可以创建自解压shell存档(您可以使用--help标志调用它以了解如何使用它):
dash
--help
#!/bin/dash ExtractFirstAndLastPathComponent () { eval current_path="\"\$$1\"" first_path_component="" last_path_component="" if [ -n "$current_path" ]; then #Remove trailing '/': temp="${current_path%?}" while [ "${current_path#"$temp"}" = "/" ]; do current_path="${current_path%?}" temp="${current_path%?}" done first_path_component="${current_path%"/"*}" if [ -z "$first_path_component" ]; then last_path_component="${current_path#'/'}" else last_path_component="${current_path#"$first_path_component""/"}" fi fi eval $2="\"\$first_path_component\"" eval $3="\"\$last_path_component\"" } ConvertToFullPath () { initial_dir_ctfp="$PWD" eval current_path="\"\$$1\"" lpc_current_path="" ExtractFirstAndLastPathComponent current_path fpc_current_path lpc_current_path if [ -n "$fpc_current_path" ]; then if [ -d "$fpc_current_path" ]; then cd "$fpc_current_path" fpc_current_path="$PWD" fi fi if [ -n "$lpc_current_path" ]; then eval $2="\"\$fpc_current_path/\$lpc_current_path\"" else eval $2=\"\/\" fi cd "$initial_dir_ctfp" } GetFileEncodingAndSizeInBytes () { eval file_to_test=\"\$$1\" GetFileSizeInBytes file_to_test file_to_test_size_in_bytes #Get file mime encoding: if [ -d "$file_to_test" ]; then result="directory" file_to_test_size_in_bytes="0" elif [ ! "$file_to_test_size_in_bytes" -eq "0" ]; then file_mime_type="$(file -bL --mime-encoding "$file_to_test" 2>/dev/null)" || { file_mime_type="undetermined"; } case "$file_mime_type" in *"binary"* ) #Only binary files containing the NULL character (^@) are considered binaries in this script: (cat -v "$file_to_test" | sed "/\^@/i'\^@'\$NL2") | { grep -q "\^@"; } && result="binary" || result="ascii" ;; *"ascii"* ) result="ascii" ;; *"utf"* ) result="${file_mime_type##*" "}" ;; * ) result="undetermined" ;; esac else result="ascii" fi eval $2=\"\$result\" eval $3=\"\$file_to_test_size_in_bytes\" } GetOSType () { case "$(uname -s)" in *"Darwin"* | *"BSD"* ) eval $1="BSD-based" ;; *"Linux"* ) eval $1="Linux" ;; * ) eval $1="Other" ;; esac } GetFileSizeInBytes () { eval file="\"\$$1\"" [ -z "$OS_TYPE" ] && GetOSType OS_TYPE if [ "$OS_TYPE" = "BSD-based" ]; then file_size_in_bytes=$(stat -Lf %z -- "$file") 2>/dev/null || { file_size_in_bytes="-1"; } elif [ "$OS_TYPE" = "Linux" ] || [ "$OS_TYPE" = "Other" ]; then file_size_in_bytes=$(stat -c %s -- "$file") 2>/dev/null || { file_size_in_bytes="-1"; } fi eval $2="$file_size_in_bytes" } PrintErrorExtra () { { if [ ! "$error" = "true" ]; then for cc in $(seq 1 $params_0); do eval current_param_func=\"\$params_$cc\" printf '\n%s\n' "Parameter $cc: '$current_param_func'" done if [ -n "$find_parameters" ]; then printf '\n%s\n\n' "Find parameters: $find_parameters" fi fi }>&2 } PrintInTitle () { printf "\033]0;%s\007" "$1" } PrintJustInTitle () { PrintInTitle "$1">"$print_to_screen" } trap1 () { printf "\n""Archiving: Aborted.\n">"$print_to_screen" CleanUp #kill all children processes, suppressing "Terminated" message: kill -s PIPE -- -$$ 2>/dev/null exit } CleanUp () { #Restore "INTERRUPT" (CTRL-C) and "TERMINAL STOP" (CTRL-Z) signals: trap - INT trap - TSTP #Clear the title: printf "\033]0;%s\007" "">"$print_to_screen" #Restore initial IFS: unset IFS #Restore initial directory: cd "$initial_dir" CheckPause } CheckPause () { #For not running from command line: pause the script after finishing: if [ "$params_0" = "1" ] && [ "$flag_count" -eq "0" ]; then printf '%s\n' "Press Enter to exit...">"$print_to_screen" read temp fi } DisplayHelp () { printf '%s\n' " TEXT project ARCHiver - a dash shell based script to archive a folder and its content as a self extracting SHell script ARchive (SHAR) (plain text)" printf '%s\n' " " printf '%s\n' " Archiving:" printf '%s\n' " - Parameters syntax: <input_folder_path> [ <archive_name> <output_folder_path> ] [ <flags> ]" printf '%s\n' " " printf '%s\n' " - what it does:" printf '%s\n' " - archives <input_folder> as <archive_name>.<default_extension> in the specified <output_folder_path>" printf '%s\n' " - if <archive_name> is an empty string (\"\") / empty: it is considered the name of the <input_folder>" printf '%s\n' " - if <output_folder_path> is an empty string (\"\") / empty: it is considered the path of the parent directory of <input_folder>" printf '%s\n' " - for archiving: <flags> can be:" printf '%s\n' " - --text-mode" printf '%s\n' " - archive only text files" printf '%s\n' " - --find-parameters / -f" printf '%s\n' " - all the parameters given after the '--find-parameters' flag, are considered 'find' parameters (denoted here as <find_parameters>)" printf '%s\n' " - <find_parameters> can be: any parameters that can be passed to the 'find' utility (which is used internally by this script) - such as: name/path filters" printf '%s\n' " - usage:" printf '%s\n' " " printf '%s\n' " Extraction:" printf '%s\n' " - Parameters syntax: <input_archive_path> [ <extraction_output_name> <extraction_output_path> ] [ <flags> ]" printf '%s\n' " " printf '%s\n' " - what it does:" printf '%s\n' " - extracts <input_archive> to the <extraction_output_path>/<extraction_output_name> path; if non-empty: <extraction_output_name> is created" printf '%s\n' " - if <extraction_output_name>: is empty/not provided -> it is considered blank; is an empty string (\"\") -> it is considered the name of the <input_archive>" printf '%s\n' " - if <extraction_output_path> is an empty string (\"\") / empty / not provided: it is considered the path of the parent directory of <input_archive>" printf '%s\n' " " printf '%s\n' " General <flags> can be:" printf '%s\n' " -h / --help - displays this help information" printf '%s\n' " -v / --verbose - displays the current file progress on the terminal screen (by default only errors are displayed)" printf "\n" } CheckUtilities () { #Check if any of the necessary utilities is missing: error="false" for utility; do man -f $utility >/dev/null 2>/dev/null || { ErrorMessage "ERROR: the '$utility' utility is not installed!"; } done>&2 if [ "$error" = "true" ]; then CleanUp; exit 1 fi } ReadRaw () { initial_IFS="$IFS" IFS= read -r $1 #Restore initial IFS: unset IFS eval value=\"\$$1\" if [ -n "$value" ]; then printf "\n"; fi } ReadArchiveParameters () { { printf '\n%s\n' "Archiving: Please provide the ouput archive name (default = Enter = archive's parent directory name):" ReadRaw archive_name printf '%s\n' "Archiving: Please provide the output archive folder path (default = Enter = the parent folder path of the archive):" ReadRaw output_path if [ -z "$find_parameters" ]; then printf '%s\n' "Archiving: Please provide find parameters (default = Enter = <none>):" ReadRaw find_parameters fi }>$print_to_screen } ErrorMessage () { if [ "$error" = "false" ]; then PrintErrorExtra fi printf '\n%s\n' "$1">&2 error="true" } PrintFileStart () { printf "\n" printf '%s\n' "### >>> START: FILE $k ($1):" printf "\t\n" printf '\t%s\n' "current_error=\"false\";" printf "\t\n" } PrintFileEnd () { printf '\t%s\n' "[ \"\$current_error\" = \"true\" ] && { error=\"true\"; printf \"ERROR: Extraction: FILE $k: Could not write to: \\\"\$BASE_PATH/\$crt_file_or_dir_path_escaped\\\"\"; }>&2" printf "\n" printf '%s\n' "### <<< END: FILE $k ($1):" printf "\n" } PrintExtractingFileMessage () { printf '\t%s\n' "PrintJustInTitle \"Extracting file $k - size $current_file_size_in_bytes"" B...\"" printf '\t%s\n' "[ \"\$verbose_flag\" = \"1\" ] && printf \"%s\\n\" \"Extracting file $k ($1): \\\"\$BASE_PATH/\$crt_file_or_dir_path_escaped\\\" - size: $current_file_size_in_bytes Bytes...\">\$print_to_screen" } print_to_screen="/dev/tty" #print to screen only initial_dir="$PWD" NL2=$(printf '%s' "\n\n") #Store New Line for use with sed #Trap "INTERRUPT" (CTRL-C) and "TERMINAL STOP" (CTRL-Z) signals: trap 'trap1' INT trap 'trap1' TSTP #For use with xxd: max number of lines stored at a time by the 'cat' utility: MAX_LINES_NUMBER=500000 #approx. 30 MB #The maximum amount of memory in bytes, that the 'cat' utility can use, when archiving a text file as plain text: MAX_TEXT_FILE_SIZE_IN_BYTES="2000000000" #approx. 2 GB #Define archive extension here: archive_extension="textarch" #Enable globbing (POSIX compliant): set +f eval ARCHIVE_SIGNATURE_STRING=\"\T\E\X\T\A\R\C\H\I\V\E\" #Store and process parameters: help_flag="0" verbose_flag="0" text_flag="0" find_parameters_flag="0" params="" find_parameters="" selected_params_count=0 flag_count="0" for param; do if [ "$find_parameters_flag" = "0" ]; then case "$param" in "-v" | "--verbose" | "--help" | "--text-mode" | "-t" | "--find-parameters" | "-f" ) case "$param" in "-v" | "--verbose" ) verbose_flag="1" ;; "--help" ) help_flag="1" ;; "--text-mode" | "-t" ) text_flag="1" ;; "--find-parameters" | "-f" ) find_parameters_flag="1" ;; esac flag_count=$(($flag_count + 1)) ;; * ) selected_params_count=$(($selected_params_count+1)) eval selected_params_$selected_params_count=\"\$param\" ;; esac else if [ "$find_parameters_flag" = "1" ]; then if [ -z "$find_parameters" ]; then find_parameters="$param" else if [ ! "$param" = "${param%*"*"*}" ] || [ ! "$param" = "${param%*"?"*}" ]; then # * and ? characters are treated literaly find_parameters="$find_parameters ""'""$param""'" else find_parameters="$find_parameters ""$param" fi fi fi fi done selected_params_0="$selected_params_count" if [ -z "$find_parameters" ]; then find_parameters='-name "*"' fi if [ "$help_flag" = "1" ] || [ "$selected_params_0" = "0" ]; then DisplayHelp exit 0 fi for i in $(seq 1 $selected_params_0); do eval params_$i=\"\$selected_params_$i\" #Store parameters other than flags done params_0=$selected_params_0 CheckUtilities find sed cat file sort chmod grep mkdir iconv stat #Process parameters/flags and check for errors: eval find /dev/null $find_parameters>/dev/null || { PrintErrorExtra printf "%s\n\n" "ERROR: Invalid find parameters provided after the \"--find-parameters\" flag!">&2 CleanUp; exit 1 } if [ "$params_0" -ge "1" ]; then { error="false" if [ "$params_0" -gt "3" ]; then ErrorMessage "ERROR: Archiving: Expected 1 or 3 parameters: <input_folder_path> [ <archive_name> <output_folder_path> ]!" else input_folder_path="$params_1" if [ "$params_0" -eq "3" ]; then archive_name="$params_2" output_path="$params_3" elif [ "$params_0" -eq "2" ]; then ErrorMessage "ERROR: Archiving: Expected 1 or 3 parameters: <input_folder_path> [ <archive_name> <output_folder_path> ]!" elif [ "$params_0" -eq "1" ]; then ReadArchiveParameters fi if [ -n "$archive_name" ] && [ ! "${archive_name%*"/"*}" = "$archive_name" ]; then ErrorMessage "ERROR: Archiving: Second provided non-flag parameter must be the name of the archive (must not contain '/')!" fi if [ -n "$output_path" ] && [ "${output_path%*"/"*}" = "$output_path" ]; then ErrorMessage "ERROR: Archiving: Third provided non-flag parameter must be the output path of the archive (must either be blank or contain '/')!" fi fi if [ "$error" = "true" ]; then CleanUp; exit 1 fi ConvertToFullPath input_folder_path input_folder_full_path ExtractFirstAndLastPathComponent input_folder_full_path fpc_input_folder_path lpc_input_folder_path if [ -z "$archive_name" ] || [ "$archive_name" = "\"\"" ]; then archive_name="$lpc_input_folder_path" fi if [ -z "$output_path" ]; then output_path="$fpc_input_folder_path" if [ -z "$fpc_input_folder_path" ]; then output_path='/' fi fi archive_name_plus_ext="$archive_name"."$archive_extension" if [ ! "$input_folder_full_path" = "/" ]; then input_folder_parent_dir_path="$fpc_input_folder_path" fi if [ ! -d "$input_folder_path" ]; then ErrorMessage "ERROR: Archiving: The path provided as the first non-flag parameter: \"$input_folder_path\" is not a valid directory path or is not accessible!" fi if [ ! -d "$output_path" ]; then ErrorMessage "ERROR: Archiving: Output path: \"$output_path\" is not a valid directory path or is not accessible!" fi if [ "$error" = "true" ]; then CleanUp; exit 1 fi }>&2 ConvertToFullPath output_path output_path #Remove trailing '/' from output_path: ExtractFirstAndLastPathComponent output_path fpc_output_path lpc_output_path if [ ! "$output_path" = '/' ]; then output_path="$fpc_output_path/$lpc_output_path" fi cd "$initial_dir" elif [ "$params_0" -eq "0" ]; then DisplayHelp exit 0 fi params_0=$selected_params_0 if [ "$error" = "true" ]; then printf "\n\n">&2 CleanUp; exit 1 fi cd "$initial_dir" ### DEFINE OUTPUT FILE: ### if [ "$output_path" = '/' ]; then output_file="/$archive_name_plus_ext" else output_file="$output_path/$archive_name_plus_ext" fi if [ -e "$output_file" ]; then ErrorMessage "ERROR: Archiving: Output file: \"$output_file\" already exists!" CleanUp; exit 1 fi ConvertToFullPath output_file output_file_full_path ### { cat /dev/null>>"$output_file_full_path"; } 2>/dev/null || { ErrorMessage "ERROR: Archiving: Could not write to output file: \"$output_file\"!" CleanUp; exit 1 } { { ExtractFirstAndLastPathComponent input_folder_full_path fpc_input_folder_full_path lpc_input_folder_full_path cat <<'EOF' #!/bin/dash EOF printf '%s\n' "### $ARCHIVE_SIGNATURE_STRING ARCHIVE ###" cat<<'EOF' ### >>> START: ARCHIVE HEADER PrintInTitle () { printf "\033]0;%s\007" "$1" } PrintJustInTitle () { PrintInTitle "$1">"$print_to_screen" } ReadRaw () { initial_IFS="$IFS" IFS= read -r $1 #Restore initial IFS: unset IFS } CheckUtilities () { #Check if any of the necessary utilities is missing: error="false" for utility; do man -f $utility >/dev/null 2>/dev/null || { ErrorMessage "ERROR: the '$utility' utility is not installed!"; } done>&2 if [ "$error" = "true" ]; then CleanUp2; exit 1 fi } trap2 () { CleanUp2 printf "\n""Extraction: Aborted.\n">"$print_to_screen" printf '%s\n' "Press Enter to exit...">"$print_to_screen" read temp #kill all children processes, suppressing "Terminated" message: kill -s PIPE -- -$$ 2>/dev/null } CleanUp2 () { #Restore "INTERRUPT" (CTRL-C) and "TERMINAL STOP" (CTRL-Z) signals: trap - INT trap - TSTP cd "$initial_dir" CheckPause } CheckPause () { if [ -z "$params_1" ] && [ -z "$params_2" ]; then printf '%s\n' "Press Enter to exit...">"$print_to_screen" read temp fi } ReadExtractParameters () { { printf '\n%s\n' "Extraction: Please provide the name of the folder where to extract the archive (this will be created) (default = blank = <empty>):" ReadRaw extraction_output_name if [ -n "$extraction_output_name" ]; then printf "\n"; fi printf '%s\n' "Extraction: Please provide the parent directory path of the folder where to extract the archive (default = blank = <archive parent directory path>):" ReadRaw extraction_output_path if [ -n "$extraction_output_path" ]; then printf "\n"; fi }>$print_to_screen } ErrorMessage () { if [ "$error" = "false" ]; then PrintErrorExtra fi printf '\n%s\n' "$1">&2 error="true" } PrintErrorExtra () { { if [ ! "$error" = "true" ]; then for cc in $(seq 1 $params_0); do eval current_param_func=\"\$params_$cc\" printf '\n%s\n' "Parameter $cc: '$current_param_func'" done printf "\n" fi }>&2 } ArchivingOutputFileWriteError () { ErrorMessage "ERROR: Archiving: Could not write to output file: \"$output_file\"!" CleanUp2; exit 1 } print_to_screen="/dev/tty" initial_dir="$PWD" #Trap "INTERRUPT" (CTRL-C) and "TERMINAL STOP" (CTRL-Z) signals: trap 'trap2' INT trap 'trap2' TSTP stored_archive_size_in_bytes="000000000000000" verbose_flag="0" i=0 params_0=0 for param; do case "$param" in "-v" | "--verbose" ) verbose_flag="1" ;; * ) i=$(($i+1)) eval params_$i=\"\$param\" ;; esac done params_0="$i" CheckUtilities mkdir iconv sed rm cd "${0%/*}" 2>/dev/null current_script_path="$(pwd -P)/${0##*/}" input_archive_path="$current_script_path" cd "$initial_dir" lpc_input_archive_path="${input_archive_path##*"/"}" fpc_input_archive_path="${input_archive_path%"$lpc_input_archive_path"*}" { [ ! "$params_1" = "${params_1%*"/"*}" ] && params_1_is_full_path="false" [ ! "$params_2" = "${params_2%*"/"*}" ] && params_2_is_full_path="false" if [ ! "$params_0" -eq "0" ]; then if [ "$params_0" -le "2" ]; then if [ "$params_1_is_full_path" = "$params_2_is_full_path" ]; then ErrorMessage "ERROR: Extraction: If 2 parameters provided: one should be a name (should not contain '/') and the other should be a path (should contain '/')!" CleanUp2; exit 1 elif [ "$params_1_is_full_path" = "false" ]; then extraction_output_name="$params_1" extraction_output_path="$params_2" else extraction_output_name="$params_2" extraction_output_path="$params_1" fi else ErrorMessage "ERROR: Extraction: Expected between 0 and 2 parameters!" CleanUp2; exit 1 fi else ReadExtractParameters fi if [ "$extraction_output_name" = \"\" ]; then extraction_output_name="${lpc_input_archive_path%"."*}" fi if [ "$extraction_output_path" = "\"\"" ] || [ -z "$extraction_output_path" ]; then extraction_output_path="$fpc_input_archive_path" fi error="false" if [ -n "$extraction_output_path" ]; then cd "$extraction_output_path" 2>/dev/null && { if [ -e "$extraction_output_name" ]; then ErrorMessage "ERROR: Extraction: The provided folder name: \"$extraction_output_name\" already exists as a directory in the specified location: \"$extraction_output_path\"!" CleanUp2; exit 1 elif [ -n "$extraction_output_name" ]; then mkdir "$extraction_output_name" 2>/dev/null || { ErrorMessage "ERROR: Extraction: Could not create directory: \"$extraction_output_name\" to the extraction output path: \"$extraction_output_path\"!" CleanUp2; exit 1 } cd "$extraction_output_name" fi } || { ErrorMessage "ERROR: Extraction: Could not access extraction output path: \"$extraction_output_path\"!" CleanUp2; exit 1 } fi BASE_PATH="$PWD" if [ ! -w "$BASE_PATH" ]; then ErrorMessage "ERROR: Extraction: Cannot extract archive: BASE_PATH directory \"$BASE_PATH\" is not writable\"!" CleanUp2; exit 1 fi } EOF }>"$output_file"; } 2>/dev/null || ArchivingOutputFileWriteError ExtractFirstAndLastPathComponent output_file_full_path fpc_output_file_full_path lpc_output_file_full_path ExtractFirstAndLastPathComponent input_folder_full_path fpc_input_folder_full_path lpc_input_folder_full_path if [ "$verbose_flag" = "1" ]; then printf "\n" fi if [ ! "$input_folder_full_path" = "/" ]; then if [ "$fpc_input_folder_full_path" = "" ]; then input_folder_name="/$lpc_input_folder_full_path" else input_folder_name="./$lpc_input_folder_full_path" fi else input_folder_name="" fi { { cat<<EOF if [ -z "\$extraction_output_name" ] && [ -e "$input_folder_name" ]; then ErrorMessage "ERROR: Extraction: Could not create directory: \"$input_folder_name\" to the extraction output path: \"\$extraction_output_path\" - directory already exists!" CleanUp2; exit 1 fi error="false" Q="'" EOF }>>"$output_file"; } 2>/dev/null || ArchivingOutputFileWriteError Q="'" IFS=' ' j=0 k=0 error="false" { cat<<'EOF' cd "$BASE_PATH" ### <<< END: ARCHIVE HEADER EOF }>>"$output_file_full_path" if [ "$verbose_flag" = "1" ]; then printf "\n" fi cd "$initial_dir" if [ "$error" = "true" ]; then printf "\n">&2 ; fi #Proceed to Archiving files: cd "$input_folder_full_path" output_file_relative_path=".${output_file_full_path#$input_folder_full_path}" j=0 k=0 for line in $(eval find . \\\( -type d -o -type f \\\) -a ! -path '.' -a ! -path './' -a ! -path "\"\$output_file_relative_path\"" $find_parameters 2>/dev/null|sort); do j=$(($j+1)) current_file_or_dir="$input_folder_parent_dir_path/$input_folder_name/$line" GetFileEncodingAndSizeInBytes current_file_or_dir current_file_encoding current_file_size_in_bytes current_file_size_in_bytes_padded="$(printf '%012d' "$current_file_size_in_bytes")" PrintJustInTitle "Analyzing/Archiving: file path $j - size $current_file_size_in_bytes_padded"" B..." current_file_or_dir2="$input_folder_name/$line" crt_file_or_dir_path2_escaped="$(printf '%s\n' "$current_file_or_dir2"|sed "s/'/$Q\"\$Q\"$Q/g")" if [ ! -r "$current_file_or_dir" ]; then printf '%s\n' "ERROR: Archiving: File not accessible/readable: \"$current_file_or_dir2\"!">&2 error="true" fi if [ -d "$current_file_or_dir" ]; then if [ "$verbose_flag" = "1" ]; then printf '%s\n' "Archiving file $j (dir): \"$current_file_or_dir2\"..." fi k=$(($k+1)) { { PrintFileStart "DIR" printf "\t%s\n" "crt_file_or_dir_path_escaped='$crt_file_or_dir_path2_escaped'" printf "\t\n" PrintExtractingFileMessage dir printf '\t%s\n' "mkdir -p \"\$BASE_PATH/\$crt_file_or_dir_path_escaped\" 2>/dev/null || { current_error=\"true\"; }" PrintFileEnd "DIR" }>>"$output_file_full_path"; } 2>/dev/null || ArchivingOutputFileWriteError elif [ -f "$current_file_or_dir" ]; then if [ "$verbose_flag" = "1" ]; then printf '%s\n' "Archiving file $j (file): \"$current_file_or_dir2\" - size: $current_file_size_in_bytes Bytes..." fi if [ ! "$current_file_encoding" = "binary" ] && [ "$current_file_size_in_bytes" -le "$MAX_TEXT_FILE_SIZE_IN_BYTES" ]; then k=$(($k+1)) { { PrintFileStart "TEXT FILE" printf "\t%s\n" "crt_file_or_dir_path_escaped='$crt_file_or_dir_path2_escaped'" printf "\t\n" PrintExtractingFileMessage file printf '\t%s\n' "mkdir -p \"\$BASE_PATH/\${crt_file_or_dir_path_escaped%\"/\"*}\" && {" printf '\t\t%s\n' "{" printf '\t\t\t%s\n' "{" printf '\t\t\t\t%s\n' "cat<<'EOFxxyyzzzzyyxx'" utf8="false"; utf="false" case "$current_file_encoding" in *"utf-8"* ) utf8="true" ;; *"utf"* ) utf="true" ;; esac if [ "$utf8" = "true" ]; then cat "$current_file_or_dir" elif [ "$utf" = "true" ]; then iconv -f "$current_file_encoding" -t utf8 "$current_file_or_dir" else cat "$current_file_or_dir" fi|sed 's/EOFxxyyzzzzyyxx/eEOFxxyyzzzzyyxx/g' printf '\n%s\n' 'EOFxxyyzzzzyyxx' printf '\t\t\t%s\n' "}|sed 's/eEOFxxyyzzzzyyxx/EOFxxyyzzzzyyxx/g'>>\"\$BASE_PATH/\$crt_file_or_dir_path_escaped\"" printf '\t\t%s' "} 2>/dev/null" printf '%s\n' " && {" printf '\t\t\t%s\n' "perl -pi -e 'chomp if eof' \"\$BASE_PATH/\$crt_file_or_dir_path_escaped\" 2>/dev/null || sed -i -z 's/\(\n\)$//' \"\$BASE_PATH/\$crt_file_or_dir_path_escaped\" 2>/dev/null" printf '\t\t%s\n' "} || current_error=\"true\"" if [ "$utf" = "true" ]; then printf '\t\t%s\n' "iconv -f utf-8 -t \"$current_file_encoding\" \"\$BASE_PATH/\$crt_file_or_dir_path_escaped\" -o \"\$BASE_PATH/\$crt_file_or_dir_path_escaped\"" fi printf '\t%s\n' "} || current_error=\"true\"" PrintFileEnd "TEXT FILE" }>>"$output_file_full_path"; } 2>/dev/null || ArchivingOutputFileWriteError elif [ "$text_flag" = "0" ]; then k=$(($k+1)) { PrintFileStart "HEX ENCODED FILE" printf "\t%s\n\n" "crt_file_or_dir_path_escaped='$crt_file_or_dir_path2_escaped'" PrintExtractingFileMessage file printf '\t%s\n' "mkdir -p \"\$BASE_PATH/\${crt_file_or_dir_path_escaped%\"/\"*}\" && {" printf '%s\n' "cat<<'EOFxxyyzzzzyyxx'" if [ "$current_file_size_in_bytes" -le "100000000000" ]; then xxd -p "$current_file_or_dir"|{ awk '\ { print $0; }; \ (NR%'"$MAX_LINES_NUMBER"'==0){ \ print "EOFxxyyzzzzyyxx"; \ print "}\x3e\x3e\x22$BASE_PATH/$crt_file_or_dir_path_escaped"".tempxyzzyx\x22"; \ print "{ cat\x3c\x3c\x27EOFxxyyzzzzyyxx\x27"; \ }; \ ' } fi printf '%s\n' "EOFxxyyzzzzyyxx" printf "\t%s" "}>>\"\$BASE_PATH/\$crt_file_or_dir_path_escaped"".tempxyzzyx\"" printf '%s\n' " || current_error=\"true\"" printf '\t%s'"\n" "xxd -p -r \"\$BASE_PATH/\$crt_file_or_dir_path_escaped.tempxyzzyx\" \"\$BASE_PATH/\$crt_file_or_dir_path_escaped\" && {" printf '\t\t%s'"\n" "rm \"\$BASE_PATH/\$crt_file_or_dir_path_escaped.tempxyzzyx\"" printf '\t%s'"\n" "}" PrintFileEnd "HEX ENCODED FILE" }>>"$output_file_full_path" || ArchivingOutputFileWriteError elif [ "$current_file_encoding" = "binary" ]; then printf '\n%s\n' "WARNING: Archiving: Skipping binary file: \"$current_file_or_dir2\"">&2 fi fi done if [ ! "$file_or_dir_path_0" = "0" ]; then if [ "$verbose_flag" = "1" ]; then printf "\n"; fi else printf '\n%s\n\n' "WARNING: Archiving: No file match - so no files where added to the archive...">&2 fi { cat<<EOF ### >>> START: ARCHIVE FOOTER ### TOTAL FILES COUNT: $k PrintJustInTitle "Extraction: Done." printf '\n%s' "Extraction: Done." if [ "\$error" = "true" ]; then printf '%s\n' " Errors were encountered!" elif [ "\$error" = "false" ]; then printf '%s\n' " No errors were encountered!" fi CleanUp2 ### <<< END: ARCHIVE FOOTER EOF }>>"$output_file_full_path" #Make the generated archive a read-only file for all users: chmod a=r "$output_file" #Make the generated archive an executable file for all users: chmod a+x "$output_file" PrintJustInTitle "Archiving: Done." printf "\n" printf '%s' "Archiving: Done." if [ "$error" = "true" ]; then printf '%s\n' " Errors were encountered!" elif [ "$error" = "false" ]; then printf '%s\n' " No errors were encountered!" fi printf "\n" CleanUp
5条答案
按热度按时间ulydmbyx1#
另一种变体是使用标记结束shell脚本,使用sed剪切shell脚本本身。
脚本
selfextract.sh
:使用方法:
woobm2wo2#
由于shell脚本不是编译的,而是逐语句执行的,所以可以使用如下模式混合二进制和文本内容(未经测试):
您可以将这两行添加到几乎任何tar+gzip文件的顶部,使其具有自解压功能。
要测试:
nnsrf1az3#
一些关于如何做到这一点的好文章可以在以下网站找到:
fslejnso4#
是的,你可以用
xtar
原生地完成它。1.构建
xtar
elf64 tar自解压头(你可以自由修改它以支持elf32,pe等可执行格式),它基于轻量级bsdtar untar和std elf lib.1.将
xtar
二进制文件复制到yourTar.xtar
1.将
yourTar.tar
存档追加到yourTar.xtar
的末尾piah890a5#
下面是一个
dash
脚本(应该可以在Linux和Mac OS-es上开箱即用),它可以创建自解压shell存档(您可以使用--help
标志调用它以了解如何使用它):