Skip to content

Instantly share code, notes, and snippets.

@koraysels
Last active June 16, 2025 09:55
Show Gist options
  • Save koraysels/acf14bee12ae15fcd48788929ebe67e1 to your computer and use it in GitHub Desktop.
Save koraysels/acf14bee12ae15fcd48788929ebe67e1 to your computer and use it in GitHub Desktop.
smart unzip of canvas quiz submissions
#!/bin/bash
# Function to check if required tools are available
check_dependencies() {
local missing_tools=()
if ! command -v unzip &> /dev/null; then
missing_tools+=("unzip")
fi
if ! command -v unrar &> /dev/null && ! command -v rar &> /dev/null && ! command -v unar &> /dev/null; then
missing_tools+=("unrar, rar, or unar")
fi
if ! command -v 7z &> /dev/null && ! command -v 7za &> /dev/null; then
missing_tools+=("7z or 7za")
fi
if [[ ${#missing_tools[@]} -gt 0 ]]; then
echo "Warning: Some extraction tools are missing:"
printf ' - %s\n' "${missing_tools[@]}"
echo ""
echo "Install missing tools:"
echo " Ubuntu/Debian: sudo apt install unzip unrar-free p7zip-full"
echo " CentOS/RHEL: sudo yum install unzip unrar p7zip"
echo " macOS: brew install unzip unar p7zip"
echo ""
fi
}
# Function to get archive type from extension
get_archive_type() {
local file="$1"
local extension="${file##*.}"
echo "$extension" | tr '[:upper:]' '[:lower:]' # Convert to lowercase
}
# Function to get base name without archive extension
get_base_name() {
local file="$1"
local base_name=$(basename "$file")
# Remove various archive extensions
if [[ "$base_name" =~ \.(zip|rar|7z)$ ]]; then
echo "${base_name%.*}"
else
echo "$base_name"
fi
}
# Function to extract archive based on type
extract_archive() {
local archive_file="$1"
local temp_dir="$2"
local archive_type=$(get_archive_type "$archive_file")
case "$archive_type" in
"zip")
if command -v unzip &> /dev/null; then
unzip -q "$archive_file" -d "$temp_dir" 2>/dev/null
else
echo "Error: unzip not found"
return 1
fi
;;
"rar")
if command -v unar &> /dev/null; then
# macOS preferred tool - unar extracts to current directory by default
(cd "$temp_dir" && unar -q "$archive_file" 2>/dev/null)
elif command -v unrar &> /dev/null; then
unrar x -inul "$archive_file" "$temp_dir/" 2>/dev/null
elif command -v rar &> /dev/null; then
rar x -inul "$archive_file" "$temp_dir/" 2>/dev/null
else
echo "Error: unar/unrar/rar not found"
return 1
fi
;;
"7z")
if command -v 7z &> /dev/null; then
7z x "$archive_file" -o"$temp_dir" -y >/dev/null 2>&1
elif command -v 7za &> /dev/null; then
7za x "$archive_file" -o"$temp_dir" -y >/dev/null 2>&1
else
echo "Error: 7z/7za not found"
return 1
fi
;;
*)
echo "Error: Unsupported archive type: $archive_type"
return 1
;;
esac
}
# Function to extract archive and rename folder
smart_extract() {
local archive_file="$1"
# Check if archive file exists
if [[ ! -f "$archive_file" ]]; then
echo "Error: Archive file '$archive_file' not found."
return 1
fi
# Check if it's a supported archive type
local archive_type=$(get_archive_type "$archive_file")
if [[ ! "$archive_type" =~ ^(zip|rar|7z)$ ]]; then
echo "Error: '$archive_file' is not a supported archive type (zip, rar, 7z)."
return 1
fi
# Get the base name without archive extension
local base_name=$(get_base_name "$archive_file")
# Extract the prefix before the first sequence of numbers
# This regex matches everything up to (but not including) the first digit
local new_name=$(echo "$base_name" | sed 's/[0-9].*//')
# Remove trailing underscore if present
new_name=$(echo "$new_name" | sed 's/_$//')
# Skip if target directory already exists
if [[ -d "$new_name" ]]; then
echo "Skipping '$archive_file' - directory '$new_name' already exists"
return 0
fi
echo "Extracting '$archive_file' ($(echo "$archive_type" | tr '[:lower:]' '[:upper:]'))..."
# Create a temporary directory for extraction
local temp_dir=$(mktemp -d)
# Extract to temporary directory
if ! extract_archive "$archive_file" "$temp_dir"; then
echo "Error: Failed to extract '$archive_file'"
rm -rf "$temp_dir"
return 1
fi
# Count items in temp directory (exclude hidden files starting with .)
local item_count=$(ls -1 "$temp_dir" 2>/dev/null | grep -v '^\.' | wc -l)
if [[ $item_count -eq 0 ]]; then
echo "Warning: No files extracted from '$archive_file'"
rm -rf "$temp_dir"
return 1
elif [[ $item_count -eq 1 ]]; then
# Single item - move and rename it
local extracted_item=$(ls -1 "$temp_dir" | grep -v '^\.' | head -1)
mv "$temp_dir/$extracted_item" "./$new_name"
echo "✓ Extracted and renamed to: $new_name"
else
# Multiple items - move temp dir contents to new folder
mkdir -p "./$new_name"
# Move all items except hidden files
find "$temp_dir" -mindepth 1 -maxdepth 1 ! -name '.*' -exec mv {} "./$new_name/" \;
echo "✓ Extracted multiple items to folder: $new_name"
fi
# Clean up temp directory
rm -rf "$temp_dir"
}
# Function to extract all archives in directory
extract_all() {
local archive_files=(*.zip *.rar *.7z *.ZIP *.RAR *.7Z)
local valid_archives=()
# Filter out non-existent files (when no files match the pattern)
for file in "${archive_files[@]}"; do
if [[ -f "$file" ]]; then
valid_archives+=("$file")
fi
done
# Check if any archive files exist
if [[ ${#valid_archives[@]} -eq 0 ]]; then
echo "No supported archive files (zip, rar, 7z) found in current directory."
exit 1
fi
echo "Found ${#valid_archives[@]} archive file(s) in current directory:"
for file in "${valid_archives[@]}"; do
local archive_type=$(get_archive_type "$file")
echo " $file ($(echo "$archive_type" | tr '[:lower:]' '[:upper:]'))"
done
echo ""
local success_count=0
local skip_count=0
local error_count=0
# Process each archive file
for archive_file in "${valid_archives[@]}"; do
local before_dir_count=$(ls -ld */ 2>/dev/null | wc -l)
if smart_extract "$archive_file"; then
local after_dir_count=$(ls -ld */ 2>/dev/null | wc -l)
if [[ $after_dir_count -gt $before_dir_count ]]; then
((success_count++))
else
((skip_count++))
fi
else
((error_count++))
fi
echo ""
done
echo "=== Summary ==="
echo "Successfully extracted: $success_count"
echo "Skipped (already exists): $skip_count"
echo "Errors: $error_count"
echo "Total processed: ${#valid_archives[@]}"
}
# Main execution
echo "Smart Archive Extractor (ZIP, RAR, 7Z)"
echo "======================================"
echo ""
# Check dependencies
check_dependencies
if [[ $# -eq 0 ]]; then
echo "Extracting all archive files in current directory..."
echo ""
extract_all
else
# Single file mode
archive_type=$(get_archive_type "$1")
if [[ ! "$archive_type" =~ ^(zip|rar|7z)$ ]]; then
echo "Error: '$1' is not a supported archive type (zip, rar, 7z)."
exit 1
fi
smart_extract "$1"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment