Last active
June 19, 2020 21:37
-
-
Save fonic/647011012b48d6dcd65e8725fed48b0c to your computer and use it in GitHub Desktop.
Erase/wipe, partition and format device on Linux
This file contains 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 bash | |
# ------------------------------------------------------------------------- | |
# - | |
# Erase/wipe, partition and format device - | |
# - | |
# Created by Fonic <https://github.com/fonic> - | |
# Date: 06/18/20 - 06/08/20 - | |
# - | |
# ------------------------------------------------------------------------- | |
# ------------------------------------ | |
# - | |
# Functions - | |
# - | |
# ------------------------------------ | |
# Convert bytes value to human-readable string [$1: bytes value, $2: target variable] | |
# Based on: https://unix.stackexchange.com/a/259254/399674 | |
function btos() { | |
local i=${1:-0} d="" s=0 S=("Bytes" "KiB" "MiB" "GiB" "TiB" "PiB" "EiB" "YiB" "ZiB") | |
while ((i > 1024 && s < ${#S[@]}-1)); do | |
printf -v d ".%02d" $((i % 1024 * 100 / 1024)) | |
i=$((i / 1024)) | |
s=$((s + 1)) | |
done | |
printf -v "${2}" "%s%s %s" "${i}" "${d}" "${S[s]}" | |
} | |
# Convert seconds value to human-readable string [$1: seconds value, $2: target variable] | |
function stos() { | |
local v=${1:-0} h=0 m=0 s=0 | |
h=$((v / 3600)) | |
v=$((v % 3600)) | |
m=$((v / 60)) | |
v=$((v % 60)) | |
s=${v} | |
printf -v "${2}" "%02d:%02d:%02d" "${h}" "${m}" "${s}" | |
} | |
# Handler for error trap [no arguments] | |
function error_trap() { | |
echo | |
echo -e "\e[1;31mAn error occurred, aborting.\e[0m" | |
echo | |
exit 1 | |
} | |
# Handler for erase/wipe interrupt trap [no arguments, relies on global variables] | |
function erase_int_trap() { | |
[[ -z "${filesystem_label}" ]] && filesystem_label="\"\"" | |
echo | |
echo | |
echo -e "\e[1;33mAborting by user request. To continue, use the following command line:\e[0m" | |
echo "# ${script} ${device} ${block_size} ${partition_type} ${filesystem_type} ${filesystem_label} ${bytes_written}" | |
echo | |
exit | |
} | |
# ------------------------------------ | |
# - | |
# Main Program - | |
# - | |
# ------------------------------------ | |
# Process command line | |
if (( $# < 5 )); then | |
echo | |
echo -e "\e[1mUsage:\e[0m" | |
echo "$(basename "$0") <device> <block-size> <partition-type> <filesystem-type> <filesystem-label> [<skip-bytes>]" | |
echo | |
echo "Block size (used by dd when erasing/wiping device):" | |
echo "1K = 1024 bytes, 2K = 2048 bytes, 4K = 4096 bytes, 8K = 8192 bytes, 16K = 16384 bytes, 32K = 32768 bytes, 64K = 65536 bytes, 128K = 131072 bytes, 256K = 262144 bytes, 512K = 524288 bytes" | |
echo "1M = 1048576 bytes, 2M = 2097152 bytes, 4M = 4194304 bytes, 8M = 8388608 bytes, 16M = 16777216 bytes, 32M = 33554432 bytes, 64M = 67108864 bytes, 128M = 134217728 bytes" | |
echo | |
echo "Partition type:" | |
echo "gpt, dos" | |
echo | |
echo "Filesystem type:" | |
echo "ext4, ntfs" | |
echo | |
echo "Filesystem label:" | |
echo "String (use empty string to leave unset)" | |
echo | |
echo "Skip bytes (leading bytes to skip when erasing/wiping device, may be used to continue previously interrupted session):" | |
echo "1 GB = 1073741824 bytes, 100 GB = 107374182400 bytes, 1 TB = 1099511627776 bytes, 2 TB = 2199023255552 bytes, 4 TB = 4398046511104 bytes, 8 TB = 8796093022208 bytes" | |
echo | |
exit 2 | |
fi | |
script="$0" | |
device="$1" | |
block_size="$2" | |
partition_type="${3,,}" | |
filesystem_type="${4,,}" | |
filesystem_label="$5" | |
if [[ -n "${6:+set}" ]]; then | |
skip_bytes="$6" | |
skip_blocks=$((skip_bytes / block_size)) | |
fi | |
if [[ ! -b "${device}" ]]; then | |
echo -e "\e[1;31mError: device '${device}' does not exist, aborting.\e[0m" | |
exit 2 | |
fi | |
if [[ "${partition_type}" != "gpt" && "${partition_type}" != "dos" ]]; then | |
echo -e "\e[1;31mError: unsupported partition type '${partition_type}', aborting.\e[0m" | |
exit 2 | |
fi | |
if [[ "${filesystem_type}" != "ext4" && "${filesystem_type}" != "ntfs" ]]; then | |
echo -e "\e[1;31mError: unsupported filesystem type '${filesystem_type}', aborting.\e[0m" | |
exit 2 | |
fi | |
# Setup error handling | |
set -e | |
trap "error_trap" ERR | |
# Print warning message | |
echo | |
printf "\e[1;33m!!! %-60s !!!\e[0m\n" "" | |
printf "\e[1;33m!!! %-60s !!!\e[0m\n" "DEVICE '${device}' WILL BE COMPLETELY ERASED/WIPED" | |
printf "\e[1;33m!!! %-60s !!!\e[0m\n" "DOUBLE-CHECK THAT YOU SPECIFIED THE CORRECT DEVICE" | |
printf "\e[1;33m!!! %-60s !!!\e[0m\n" "" | |
echo | |
fdisk -l "${device}" | |
echo | |
if [[ -n "${skip_bytes:+set}" ]]; then | |
btos ${skip_bytes} s1 | |
echo -e "\e[1mNOTE:\e[0m" | |
echo "Will skip first ${skip_bytes} bytes (${s1}, ${skip_blocks} blocks of ${block_size} bytes) when erasing device" | |
echo | |
fi | |
# Ask for user confirmation | |
echo -en "\e[1mType 'OK' to continue: \e[0m" | |
read input | |
if [[ "${input}" != "OK" ]]; then | |
echo | |
exit | |
fi | |
echo | |
# Erase/wipe device | |
# NOTE: using wipefs to make sure no partition table and/or filesystem ids remain even if skipping leading bytes when erasing | |
# NOTE: dd will fail with 'no space left on device'; since we're not using pipefail, there's no need to mask this error | |
echo -e "\e[1mErasing/wiping device...\e[0m" | |
echo "Started: $(date)" | |
#wipefs --all "${device}" # not working as expected; it would seem this wipes only the partition table, but not filesystem signatures; using dd instead | |
dd if=/dev/zero of=${device} bs=1M count=16 oflag=direct status=none | |
dd_opts=("if=/dev/zero" "of=${device}" "bs=${block_size}" "oflag=direct" "status=progress") | |
[[ -n "${skip_bytes:+set}" ]] && dd_opts+=("seek=${skip_blocks}") # https://utcc.utoronto.ca/~cks/space/blog/unix/DdSkipVersusSeek | |
bytes_total=$(blockdev --getsize64 "${device}") | |
bytes_written=${skip_bytes:-0} | |
bytes_left=$((bytes_total - bytes_written)) | |
bytes_per_second=0 | |
seconds_start=${SECONDS} | |
seconds_total=0 | |
seconds_elapsed=0 | |
seconds_left=0 | |
break_loop=0 | |
re_dd="([0-9]+) bytes" # dd progress output: '146577293312 bytes (147 GB, 137 GiB) copied, 2065 s, 71,0 MB/s' | |
trap "erase_int_trap" INT | |
while (( ${break_loop} == 0 )); do # done like this to make sure final dd output message ('no space left on device' + stats) is processed within loop (see below) | |
if ! read -d $'\r' -r line; then # will fail on final dd output due to missing delimiter '\r' + EOF | |
readarray -t lines <<< "${line}" # final dd output consists of multiple lines containing 2x stats among other stuff; we want the second stats line, i.e. last line of output | |
line="${lines[-1]}" | |
break_loop=1 | |
fi | |
seconds_elapsed=$((SECONDS - seconds_start)) | |
if [[ "${line}" =~ ${re_dd} ]]; then | |
dd_bytes=${BASH_REMATCH[1]} | |
bytes_written=$((${skip_bytes:-0} + dd_bytes)) | |
bytes_left=$((bytes_total - bytes_written)) | |
(( ${bytes_left} < 0 )) && bytes_left=0 # can happen on final iteration, not sure why exactly | |
(( ${seconds_elapsed} > 0 )) && bytes_per_second=$((dd_bytes / seconds_elapsed)) | |
(( ${bytes_per_second} > 0 )) && { seconds_left=$((bytes_left / bytes_per_second)); seconds_total=$((bytes_total / bytes_per_second)); } | |
btos ${bytes_written} s1 | |
btos ${bytes_left} s2 | |
btos ${bytes_total} s3 | |
stos ${seconds_elapsed} s4 | |
stos ${seconds_left} s5 | |
stos ${seconds_total} s6 | |
btos ${bytes_per_second} s7 | |
printf "\r\e[2KWritten: %s, Left: %s, Total: %s, Elapsed: %s, Left: %s, Total: %s, Speed: %s/s" "${s1}" "${s2}" "${s3}" "${s4}" "${s5}" "${s6}" "${s7}" | |
else | |
printf "\r\e[2K\e[1;33mWarning: failed to match regular expression\e[0m" | |
fi | |
done < <(dd "${dd_opts[@]}" 2>&1) | |
trap - INT | |
echo | |
echo "Finished: $(date)" | |
echo | |
# Sync and partprobe | |
echo -e "\e[1mSyncing and partprobing...\e[0m" | |
sync | |
partprobe "${device}" | |
echo | |
# Create partition table | |
# NOTE: fdisk might fail with 'rereading partition table failed', using '|| true' to mask error | |
echo -e "\e[1mCreating partition table...\e[0m" | |
if [[ "${partition_type}" == "gpt" ]]; then | |
{ | |
echo "g" # Create new empty GPT partition table | |
echo "n" # Add new partition | |
echo "" # Partition number (default: 1) | |
echo "" # First sector (default: 2048) | |
echo "" # Last sector (default: last sector of device) | |
echo "t" # Change partition type (will automatically select partition 1 if only one partition present) | |
if [[ "${filesystem_type}" == "ext4" ]]; then | |
echo "20" # Type '20 Linux filesystem' | |
elif [[ "${filesystem_type}" == "ntfs" ]]; then | |
echo "11" # Type '11 Microsoft basic data' | |
fi | |
echo "w" # Write changes to disk and quit | |
} | fdisk "${device}" || true | |
elif [[ "${partition_type}" == "dos" ]]; then | |
{ | |
echo "o" # Create new empty DOS partition table | |
echo "n" # Add new partition | |
echo "" # Partition type (default: 'primary' if less than 4 partitions present, else 'extended') | |
echo "" # Partition number (default: 1) | |
echo "" # First sector (default: 2048) | |
echo "" # Last sector (default: last sector of device) | |
echo "t" # Change partition type (will automatically select partition 1 if only one partition present) | |
if [[ "${filesystem_type}" == "ext4" ]]; then | |
echo "83" # Type '83 Linux' | |
elif [[ "${filesystem_type}" == "ntfs" ]]; then | |
echo "07" # Type '07 HPFS/NTFS/exFAT' | |
fi | |
echo "w" # Write changes to disk and quit | |
} | fdisk "${device}" || true | |
fi | |
# Sync and partprobe | |
echo -e "\e[1mSyncing and partprobing...\e[0m" | |
sync | |
partprobe "${device}" | |
echo | |
# Create filesystem | |
echo -e "\e[1mCreating filesystem...\e[0m" | |
if [[ "${filesystem_type}" == "ext4" ]]; then | |
if [[ -n "${filesystem_label}" ]]; then | |
mkfs.ext4 -L "${filesystem_label}" "${device}1" | |
else | |
mkfs.ext4 "${device}1" | |
fi | |
elif [[ "${filesystem_type}" == "ntfs" ]]; then | |
if [[ -n "${filesystem_label}" ]]; then | |
mkfs.ntfs -Q -L "${filesystem_label}" "${device}1" | |
else | |
mkfs.ntfs -Q "${device}1" | |
fi | |
fi | |
echo | |
# Sync | |
echo -e "\e[1mSyncing...\e[0m" | |
sync | |
echo | |
# Print results | |
echo -e "\e[1;32mSuccess! Results:\e[0m" | |
echo | |
echo -e "\e[1;32mfdisk:\e[0m" | |
fdisk -l "${device}" | |
echo | |
echo -e "\e[1;32mblkid:\e[0m" | |
blkid "${device}"* | |
echo |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Erase, partition and format device on Linux
I created this script to facilitate erasing/wiping used devices before selling them.
Dependencies:
Bash >= 4.0, fdisk, wipefs, dd, sync, partprobe, mkfs.ext4, mkfs.ntfs, blkid.