extract.plugin.zsh
6232 bytes
1alias x=extract
2
3extract() {
4 setopt localoptions noautopushd
5
6 if (( $# == 0 )); then
7 cat >&2 <<'EOF'
8Usage: extract [-option] [file ...]
9
10Options:
11 -r, --remove Remove archive after unpacking.
12 -t, --to-directory <dir> Extract to a specific directory instead of the current one.
13EOF
14 fi
15
16 local remove_archive=1
17 local target_directory=""
18
19 while (( $# > 0 )); do
20 case "$1" in
21 -r|--remove)
22 remove_archive=0
23 shift
24 ;;
25 -t|--to-directory)
26 shift
27 if (( $# == 0 )); then
28 echo "extract: -t/--to-directory requires a directory argument" >&2
29 return 1
30 fi
31
32 target_directory="$1"
33 shift
34
35 if [[ ! -d "$target_directory" ]]; then
36 echo "extract: '$target_directory' is not a valid directory" >&2
37 return 1
38 fi
39
40 target_directory="${target_directory%/}"
41 ;;
42 *)
43 break
44 ;;
45 esac
46 done
47
48 local pwd="$PWD"
49 while (( $# > 0 )); do
50 if [[ ! -f "$1" ]]; then
51 echo "extract: '$1' is not a valid file" >&2
52 shift
53 continue
54 fi
55
56 local success=0
57 local file="$1" full_path="${1:A}"
58 local extract_dir="${1:t:r}"
59
60 # Remove the .tar extension if the file name is .tar.*
61 if [[ $extract_dir =~ '\.tar$' ]]; then
62 extract_dir="${extract_dir:r}"
63 fi
64
65 if [[ -n "$target_directory" ]]; then
66 extract_dir="$target_directory/${extract_dir:t}"
67 fi
68
69 # If there's a file or directory with the same name as the archive
70 # add a random string to the end of the extract directory
71 if [[ -e "$extract_dir" ]]; then
72 local rnd="${(L)"${$(( [##36]$RANDOM*$RANDOM ))}":1:5}"
73 extract_dir="${extract_dir}-${rnd}"
74 fi
75
76 # Create an extraction directory based on the file name
77 command mkdir -p "$extract_dir"
78 builtin cd -q "$extract_dir"
79 echo "extract: extracting to $extract_dir" >&2
80
81 case "${file:l}" in
82 (*.tar.gz|*.tgz)
83 (( $+commands[pigz] )) && { tar -I pigz -xvf "$full_path" } || tar zxvf "$full_path" ;;
84 (*.tar.bz2|*.tbz|*.tbz2)
85 (( $+commands[pbzip2] )) && { tar -I pbzip2 -xvf "$full_path" } || tar xvjf "$full_path" ;;
86 (*.tar.xz|*.txz)
87 (( $+commands[pixz] )) && { tar -I pixz -xvf "$full_path" } || {
88 tar --xz --help &> /dev/null \
89 && tar --xz -xvf "$full_path" \
90 || xzcat "$full_path" | tar xvf - } ;;
91 (*.tar.zma|*.tlz)
92 tar --lzma --help &> /dev/null \
93 && tar --lzma -xvf "$full_path" \
94 || lzcat "$full_path" | tar xvf - ;;
95 (*.tar.zst|*.tzst)
96 tar --zstd --help &> /dev/null \
97 && tar --zstd -xvf "$full_path" \
98 || zstdcat "$full_path" | tar xvf - ;;
99 (*.tar) tar xvf "$full_path" ;;
100 (*.tar.lz) (( $+commands[lzip] )) && tar xvf "$full_path" ;;
101 (*.tar.lz4) lz4 -c -d "$full_path" | tar xvf - ;;
102 (*.tar.lrz) (( $+commands[lrzuntar] )) && lrzuntar "$full_path" ;;
103 (*.gz) (( $+commands[pigz] )) && pigz -cdk "$full_path" > "${file:t:r}" || gunzip -ck "$full_path" > "${file:t:r}" ;;
104 (*.bz2) (( $+commands[pbzip2] )) && pbzip2 -d "$full_path" || bunzip2 "$full_path" ;;
105 (*.xz) xzcat "$full_path" > "${file:t:r}" ;;
106 (*.lrz) (( $+commands[lrunzip] )) && lrunzip "$full_path" ;;
107 (*.lz4) lz4 -d "$full_path" ;;
108 (*.lzma) unlzma "$full_path" ;;
109 (*.z) uncompress "$full_path" ;;
110 (*.zip|*.war|*.jar|*.ear|*.sublime-package|*.ipa|*.ipsw|*.xpi|*.apk|*.aar|*.whl|*.vsix|*.crx|*.pk3|*.pk4) unzip "$full_path" ;;
111 (*.rar)
112 if (( $+commands[unrar] )); then
113 unrar x -ad "$full_path"
114 elif (( $+commands[unar] )); then
115 unar -o . "$full_path"
116 else
117 echo "extract: cannot extract RAR files: install unrar or unar" >&2
118 success=1
119 fi ;;
120 (*.rpm)
121 rpm2cpio "$full_path" | cpio --quiet -id ;;
122 (*.7z | *.7z.[0-9]* | *.pk7) 7za x "$full_path" ;;
123 (*.deb)
124 command mkdir -p "control" "data"
125 ar vx "$full_path" > /dev/null
126 builtin cd -q control; extract ../control.tar.*
127 builtin cd -q ../data; extract ../data.tar.*
128 builtin cd -q ..; command rm *.tar.* debian-binary ;;
129 (*.zst) unzstd --stdout "$full_path" > "${file:t:r}" ;;
130 (*.cab|*.exe) cabextract "$full_path" ;;
131 (*.cpio|*.obscpio) cpio -idmvF "$full_path" ;;
132 (*.zpaq) zpaq x "$full_path" ;;
133 (*.zlib) zlib-flate -uncompress < "$full_path" > "${file:r}" ;;
134 (*)
135 echo "extract: '$file' cannot be extracted" >&2
136 success=1 ;;
137 esac
138
139 (( success = success > 0 ? success : $? ))
140 (( success == 0 && remove_archive == 0 )) && command rm "$full_path"
141 shift
142
143 # Go back to original working directory
144 builtin cd -q "$pwd"
145
146 # If content of extract dir is a single directory, move its contents up
147 # Glob flags:
148 # - D: include files starting with .
149 # - N: no error if directory is empty
150 # - Y2: at most give 2 files
151 local -a content
152 content=("${extract_dir}"/*(DNY2))
153 if [[ ${#content} -eq 1 && -e "${content[1]}" ]]; then
154 # The extracted file/folder (${content[1]}) may have the same name as $extract_dir
155 # If so, we need to rename it to avoid conflicts in a 3-step process
156 #
157 # 1. Move and rename the extracted file/folder to a temporary random name
158 # 2. Delete the empty folder
159 # 3. Rename the extracted file/folder to the original name
160 if [[ "${content[1]:t}" == "${extract_dir:t}" ]]; then
161 # =(:) gives /tmp/zsh<random>, with :t it gives zsh<random>
162 local tmp_name==(:); tmp_name="${tmp_name:t}"
163 command mv "${content[1]}" "$tmp_name" \
164 && command rmdir "$extract_dir" \
165 && command mv "$tmp_name" "$extract_dir"
166 # Otherwise, if the extracted folder name already exists in the current
167 # directory (because of a previous file / folder), keep the extract_dir
168 elif [[ ! -e "${target_directory:-.}/${content[1]:t}" ]]; then
169 command mv -- "${content[1]}" "${target_directory:-.}/" \
170 && command rmdir -- "$extract_dir"
171 fi
172 elif [[ ${#content} -eq 0 ]]; then
173 command rmdir "$extract_dir"
174 fi
175 done
176}