Created
May 17, 2025 20:10
-
-
Save tos-kamiya/2031496d0bedf179227df11f8e6d2c17 to your computer and use it in GitHub Desktop.
A lightweight clone of unar for Windows: utilizes BusyBox to handle various archive formats, with optional support for 7-Zip and WinRAR if installed.
This file contains hidden or 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/env python3 | |
import sys | |
import os | |
import subprocess | |
import zipfile | |
import shutil | |
import argparse | |
import time | |
def is_tool_available(tool_name): | |
"""Check if a tool is available in the system PATH.""" | |
from shutil import which | |
return which(tool_name) is not None | |
def extract_with_7zip(file_path, dest_dir): | |
try: | |
subprocess.run(['7z', 'x', file_path, f'-o{dest_dir}', '-y'], check=True) | |
print(f"Extracted archive with 7-Zip: {file_path}") | |
return True | |
except (subprocess.CalledProcessError, FileNotFoundError): | |
return False | |
def extract_with_winrar(file_path, dest_dir): | |
try: | |
subprocess.run(['unrar', 'x', '-y', file_path, dest_dir], check=True) | |
print(f"Extracted archive with WinRAR: {file_path}") | |
return True | |
except (subprocess.CalledProcessError, FileNotFoundError): | |
return False | |
def extract_zip_with_python(file_path, dest_dir, force_dir, copy_time): | |
try: | |
with zipfile.ZipFile(file_path, 'r') as zip_ref: | |
names = zip_ref.namelist() | |
top_level = set(name.split('/')[0] for name in names if '/' in name) | |
if force_dir or len(top_level) != 1: | |
base_name = os.path.splitext(os.path.basename(file_path))[0] | |
dest_dir = os.path.join(dest_dir, base_name) | |
os.makedirs(dest_dir, exist_ok=True) | |
zip_ref.extractall(dest_dir) | |
print(f"Extracted ZIP archive: {file_path}") | |
if copy_time: | |
mod_time = os.path.getmtime(file_path) | |
os.utime(dest_dir, (mod_time, mod_time)) | |
except zipfile.BadZipFile: | |
print(f"Invalid ZIP file: {file_path}") | |
except Exception as e: | |
print(f"Error extracting ZIP file: {e}") | |
def extract_with_busybox(file_path, dest_dir): | |
try: | |
ext = file_path.lower() | |
if ext.endswith(('.tar.gz', '.tgz')): | |
cmd = ['busybox', 'tar', 'xvzf', file_path] | |
elif ext.endswith(('.tar.bz2', '.tbz2')): | |
cmd = ['busybox', 'tar', 'xvjf', file_path] | |
elif ext.endswith('.tar'): | |
cmd = ['busybox', 'tar', 'xvf', file_path] | |
elif ext.endswith('.gz'): | |
cmd = ['busybox', 'gunzip', '-k', file_path] | |
elif ext.endswith('.bz2'): | |
cmd = ['busybox', 'bunzip2', '-k', file_path] | |
else: | |
print(f"Unsupported file format: {file_path}") | |
return False | |
subprocess.run(cmd, check=True, cwd=dest_dir) | |
print(f"Extracted archive with BusyBox: {file_path}") | |
return True | |
except (subprocess.CalledProcessError, FileNotFoundError) as e: | |
print(f"Error extracting archive with BusyBox: {e}") | |
return False | |
def main(): | |
parser = argparse.ArgumentParser(description='bb_unar: Archive extraction tool') | |
parser.add_argument('archive', help='Archive file to extract') | |
parser.add_argument('-o', '--output-directory', default='.', help='Directory to extract files to (default: current directory). Use "-" for stdout (not implemented).') | |
parser.add_argument('-d', '--force-directory', action='store_true', help='Always create a containing directory for the extracted contents') | |
parser.add_argument('-t', '--copy-time', action='store_true', help='Copy the archive file\'s modification time to the containing directory') | |
args = parser.parse_args() | |
file_path = args.archive | |
dest_dir = args.output_directory | |
force_dir = args.force_directory | |
copy_time = args.copy_time | |
if not os.path.isfile(file_path): | |
print(f"File not found: {file_path}") | |
sys.exit(1) | |
if dest_dir == '-': | |
print("Output to stdout is not implemented.") | |
sys.exit(1) | |
if not os.path.isdir(dest_dir): | |
os.makedirs(dest_dir, exist_ok=True) | |
file_lower = file_path.lower() | |
if file_lower.endswith('.zip'): | |
if is_tool_available('7z'): | |
if not extract_with_7zip(file_path, dest_dir): | |
print("7-Zip extraction failed.") | |
sys.exit(1) | |
else: | |
extract_zip_with_python(file_path, dest_dir, force_dir, copy_time) | |
elif file_lower.endswith(('.7z', '.rar')): | |
if is_tool_available('7z'): | |
if not extract_with_7zip(file_path, dest_dir): | |
print("7-Zip extraction failed.") | |
sys.exit(1) | |
elif is_tool_available('unrar'): | |
if not extract_with_winrar(file_path, dest_dir): | |
print("WinRAR extraction failed.") | |
sys.exit(1) | |
else: | |
print("Neither 7-Zip nor WinRAR is available. Please install one of them.") | |
sys.exit(1) | |
else: | |
if not extract_with_busybox(file_path, dest_dir): | |
sys.exit(1) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment