Introduction to mv
The mv command (short for "move") is a fundamental Unix/Linux utility used for moving files and directories from one location to another. It also serves as the primary tool for renaming files and directories. Understanding mv is crucial for effective file system management.
Key Concepts
- Moving: Relocating files/directories to different directories
- Renaming: Changing names within the same directory
- Overwrite Protection: Options to prevent accidental data loss
- Atomic Operations: On the same filesystem, moves are atomic
- Cross-filesystem Moves: Actually copies then deletes
1. Basic Usage
Moving Files
# Move a single file to another directory mv file.txt /path/to/destination/ # Move multiple files to a directory mv file1.txt file2.txt file3.txt /destination/dir/ # Move using wildcards mv *.txt /destination/dir/ # Move with pattern matching mv doc[0-9].txt /destination/
Renaming Files
# Rename a file in the same directory
mv oldname.txt newname.txt
# Rename with directory change
mv oldname.txt /new/path/newname.txt
# Rename multiple files (using loop)
for file in *.txt; do
mv "$file" "${file%.txt}.bak"
done
# Rename with pattern substitution
for file in image_*.jpg; do
mv "$file" "photo_${file#image_}"
done
Moving Directories
# Move entire directory
mv sourcedir/ /destination/path/
# Move directory and rename
mv olddirname/ /new/path/newdirname/
# Move contents, not the directory itself
mv sourcedir/* /destination/dir/
# Move hidden files as well
mv sourcedir/{.,}* /destination/dir/ 2>/dev/null || true
2. Command Options
-i (Interactive) Option
# Interactive mode - prompts before overwrite
mv -i file.txt /destination/
# Example with multiple files
mv -i *.txt /target/dir/
# Prompts for each file that would be overwritten
# Script usage
mv -i "$source" "$dest" || {
echo "User cancelled or error occurred"
exit 1
}
# Safe move function
safe_move() {
local src="$1"
local dest="$2"
if [ -e "$dest" ]; then
echo "Warning: $dest already exists"
mv -i "$src" "$dest"
else
mv "$src" "$dest"
fi
}
-f (Force) Option
# Force overwrite without prompting
mv -f file.txt /destination/
# Override any previous -i option
mv -if file.txt /destination/ # -f overrides -i
# Force move even if destination is write-protected
mv -f protected.txt /dest/
# Script usage for automated operations
force_move() {
local src="$1"
local dest="$2"
if [ ! -e "$src" ]; then
echo "Error: Source not found: $src"
return 1
fi
mv -f "$src" "$dest"
echo "Moved $src to $dest"
}
-n (No Overwrite) Option
# Never overwrite existing files
mv -n file.txt /destination/
# Combine with other options
mv -nv *.txt /dest/ # Verbose no-overwrite
# Check if file exists before move
move_if_not_exists() {
local src="$1"
local dest="$2"
if [ ! -e "$dest" ]; then
mv "$src" "$dest"
return 0
else
echo "Destination already exists: $dest"
return 1
fi
}
# Using -n in scripts
for file in *.log; do
mv -n "$file" /archive/ || echo "Skipped $file (already exists)"
done
-v (Verbose) Option
# Show what's being done
mv -v file.txt /destination/
# Output: 'file.txt' -> '/destination/file.txt'
# Verbose move of multiple files
mv -v *.txt /target/
# Shows each file as it's moved
# Logging moves
log_move() {
local src="$1"
local dest="$2"
local logfile="/var/log/file-moves.log"
echo "$(date): Moving $src to $dest" >> "$logfile"
mv -v "$src" "$dest" | tee -a "$logfile"
}
# Verbose with progress indication
move_with_progress() {
local total=$(ls -1 "$@" | wc -l)
local count=0
for file in "$@"; do
if [ -f "$file" ]; then
count=$((count + 1))
echo "[$count/$total] Moving: $file"
mv -v "$file" "${@: -1}" 2>&1 | sed 's/^/ /'
fi
done
}
-u (Update) Option
# Move only if source is newer or destination missing
mv -u file.txt /destination/
# Update files conditionally
mv -u *.log /archive/
# Sync directories with update
update_directory() {
local src_dir="$1"
local dest_dir="$2"
for file in "$src_dir"/*; do
if [ -f "$file" ]; then
local basename=$(basename "$file")
local dest="$dest_dir/$basename"
mv -u "$file" "$dest_dir" && echo "Updated: $basename"
fi
done
}
# Backup only changed files
backup_changed() {
local src="$1"
local backup="$2"
if [ ! -d "$backup" ]; then
mkdir -p "$backup"
fi
find "$src" -type f -exec sh -c '
for file; do
rel="${file#$1/}"
dest="$2/$rel"
mkdir -p "$(dirname "$dest")"
mv -u "$file" "$dest"
done
' _ "$src" "$backup" {} +
}
3. Practical Examples
File Organization Scripts
#!/bin/bash
# Organize files by extension
organize_by_extension() {
local target_dir="${1:-.}"
cd "$target_dir" || exit 1
for file in *; do
if [ -f "$file" ]; then
extension="${file##*.}"
if [ "$extension" != "$file" ]; then
mkdir -p "$extension"
mv -v "$file" "$extension/"
fi
fi
done
}
# Organize files by date
organize_by_date() {
local target_dir="${1:-.}"
cd "$target_dir" || exit 1
for file in *; do
if [ -f "$file" ]; then
# Get modification date
year=$(date -r "$file" +%Y)
month=$(date -r "$file" +%m)
mkdir -p "$year/$month"
mv -v "$file" "$year/$month/"
fi
done
}
# Organize by file size
organize_by_size() {
local target_dir="${1:-.}"
cd "$target_dir" || exit 1
for file in *; do
if [ -f "$file" ]; then
size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null)
if [ "$size" -lt 1024 ]; then
mkdir -p "small"
mv -v "$file" "small/"
elif [ "$size" -lt 1048576 ]; then
mkdir -p "medium"
mv -v "$file" "medium/"
else
mkdir -p "large"
mv -v "$file" "large/"
fi
fi
done
}
# Usage examples
# organize_by_extension ~/Downloads
# organize_by_date ~/Photos
# organize_by_size ~/Documents
Batch Renaming
#!/bin/bash
# Rename files with pattern
rename_pattern() {
local pattern="$1"
local replacement="$2"
local files="${3:-*}"
for file in $files; do
if [ -f "$file" ]; then
newname=$(echo "$file" | sed "s/$pattern/$replacement/g")
if [ "$newname" != "$file" ]; then
mv -v "$file" "$newname"
fi
fi
done
}
# Add prefix to files
add_prefix() {
local prefix="$1"
local files="${2:-*}"
for file in $files; do
if [ -f "$file" ]; then
mv -v "$file" "${prefix}${file}"
fi
done
}
# Add suffix to files
add_suffix() {
local suffix="$1"
local files="${2:-*}"
for file in $files; do
if [ -f "$file" ]; then
filename="${file%.*}"
extension="${file##*.}"
if [ "$filename" != "$file" ]; then
mv -v "$file" "${filename}${suffix}.${extension}"
else
mv -v "$file" "${file}${suffix}"
fi
fi
done
}
# Convert spaces to underscores
rename_spaces() {
for file in *; do
if [[ "$file" == *" "* ]]; then
newname=$(echo "$file" | tr ' ' '_')
mv -v "$file" "$newname"
fi
done
}
# Make lowercase
rename_lowercase() {
for file in *; do
if [ -f "$file" ]; then
newname=$(echo "$file" | tr '[:upper:]' '[:lower:]')
if [ "$newname" != "$file" ]; then
mv -v "$file" "$newname"
fi
fi
done
}
# Usage examples
# rename_pattern "IMG_[0-9]{4}" "photo" "*.jpg"
# add_prefix "backup_" "*.txt"
# add_suffix "_old" "*.conf"
# rename_spaces
# rename_lowercase
Directory Management
#!/bin/bash
# Move all files from subdirectories to current directory
flatten_directory() {
local target_dir="${1:-.}"
find "$target_dir" -type f -print0 | while IFS= read -r -d '' file; do
if [ "$(dirname "$file")" != "$target_dir" ]; then
mv -v "$file" "$target_dir/"
fi
done
}
# Move files matching pattern from subdirectories
collect_files() {
local pattern="$1"
local target_dir="${2:-.}"
find . -type f -name "$pattern" -not -path "./$target_dir/*" -exec mv -v {} "$target_dir/" \;
}
# Move directory contents up one level
move_up() {
local dir="${1:-.}"
if [ -d "$dir" ]; then
(
cd "$dir" || exit 1
mv -v ./* ../ 2>/dev/null
mv -v ./.??* ../ 2>/dev/null
cd .. && rmdir "$dir" 2>/dev/null
)
fi
}
# Split directory into multiple directories by first letter
split_by_first_letter() {
local source_dir="$1"
local target_base="${2:-.}"
cd "$source_dir" || exit 1
for file in *; do
if [ -f "$file" ]; then
first_char=$(echo "${file:0:1}" | tr '[:upper:]' '[:lower:]')
mkdir -p "$target_base/$first_char"
mv -v "$file" "$target_base/$first_char/"
fi
done
}
# Usage examples
# flatten_directory ~/Downloads
# collect_files "*.pdf" ~/Documents/PDFs
# move_up ~/Downloads/temp_folder
# split_by_first_letter ~/Downloads ~/organized
4. Advanced Techniques
Conditional Moves
#!/bin/bash
# Move files based on size
move_by_size() {
local min_size="${1:-0}"
local max_size="${2:-999999999}"
local dest_dir="${3:-.}"
mkdir -p "$dest_dir"
find . -maxdepth 1 -type f -size +"$min_size"c -size -"$max_size"c -exec mv -v {} "$dest_dir/" \;
}
# Move files based on age
move_by_age() {
local days_old="$1"
local dest_dir="$2"
mkdir -p "$dest_dir"
find . -maxdepth 1 -type f -mtime +"$days_old" -exec mv -v {} "$dest_dir/" \;
}
# Move files based on content
move_by_content() {
local pattern="$1"
local dest_dir="$2"
mkdir -p "$dest_dir"
grep -l "$pattern" ./* 2>/dev/null | while read -r file; do
mv -v "$file" "$dest_dir/"
done
}
# Move empty files
move_empty_files() {
local dest_dir="$1"
mkdir -p "$dest_dir"
find . -maxdepth 1 -type f -empty -exec mv -v {} "$dest_dir/" \;
}
# Move executable files
move_executables() {
local dest_dir="$1"
mkdir -p "$dest_dir"
find . -maxdepth 1 -type f -executable -exec mv -v {} "$dest_dir/" \;
}
Atomic Operations and Safety
#!/bin/bash
# Atomic move with backup
safe_atomic_move() {
local src="$1"
local dest="$2"
if [ ! -e "$src" ]; then
echo "Error: Source does not exist: $src"
return 1
fi
# Create backup if destination exists
if [ -e "$dest" ]; then
local backup="${dest}.backup-$(date +%Y%m%d-%H%M%S)"
mv "$dest" "$backup"
echo "Existing file backed up as: $backup"
fi
# Atomic move (if on same filesystem)
if mv "$src" "$dest"; then
echo "Successfully moved to: $dest"
return 0
else
# Restore backup if move failed
if [ -n "$backup" ] && [ -e "$backup" ]; then
mv "$backup" "$dest"
fi
echo "Error: Move failed"
return 1
fi
}
# Move with transaction log
logged_move() {
local src="$1"
local dest="$2"
local logfile="${3:-/tmp/mv.log}"
echo "$(date '+%Y-%m-%d %H:%M:%S'): Moving $src to $dest" >> "$logfile"
if mv "$src" "$dest" >> "$logfile" 2>&1; then
echo "$(date '+%Y-%m-%d %H:%M:%S'): Success" >> "$logfile"
return 0
else
echo "$(date '+%Y-%m-%d %H:%M:%S'): Failed" >> "$logfile"
return 1
fi
}
# Move with checksum verification
move_with_checksum() {
local src="$1"
local dest="$2"
# Calculate source checksum
local src_sum=$(md5sum "$src" | cut -d' ' -f1)
# Move file
mv "$src" "$dest" || return 1
# Calculate destination checksum
local dest_sum=$(md5sum "$dest" | cut -d' ' -f1)
if [ "$src_sum" = "$dest_sum" ]; then
echo "Checksum verification passed"
return 0
else
echo "ERROR: Checksum verification failed!"
# Try to restore
mv "$dest" "$src"
return 1
fi
}
Parallel Moves
#!/bin/bash
# Parallel move using background jobs
parallel_move() {
local dest_dir="$1"
shift
local max_jobs="${1:-4}"
shift
mkdir -p "$dest_dir"
local job_count=0
for file in "$@"; do
if [ -e "$file" ]; then
(
mv -v "$file" "$dest_dir/"
) &
job_count=$((job_count + 1))
if [ $job_count -ge $max_jobs ]; then
wait
job_count=0
fi
fi
done
wait
echo "All moves completed"
}
# Move using xargs
xargs_move() {
local dest_dir="$1"
local pattern="${2:-*}"
mkdir -p "$dest_dir"
ls -1 $pattern | xargs -I {} -P 4 mv -v {} "$dest_dir/"
}
# Move using GNU parallel (if installed)
parallel_move_advanced() {
local dest_dir="$1"
shift
if command -v parallel >/dev/null 2>&1; then
mkdir -p "$dest_dir"
parallel mv -v {} "$dest_dir/" ::: "$@"
else
echo "GNU parallel not installed, using xargs fallback"
printf '%s\n' "$@" | xargs -P 4 -I {} mv -v {} "$dest_dir/"
fi
}
5. Error Handling and Recovery
Comprehensive Error Handling
#!/bin/bash
# Robust move function with error handling
robust_move() {
local src="$1"
local dest="$2"
local errors=()
# Check source
if [ ! -e "$src" ]; then
echo "ERROR: Source does not exist: $src" >&2
return 1
fi
# Check read permission
if [ ! -r "$src" ]; then
echo "ERROR: No read permission for: $src" >&2
return 1
fi
# Check destination directory
local dest_dir
if [ -d "$dest" ]; then
dest_dir="$dest"
dest="$dest_dir/$(basename "$src")"
else
dest_dir=$(dirname "$dest")
fi
if [ ! -d "$dest_dir" ]; then
echo "ERROR: Destination directory does not exist: $dest_dir" >&2
return 1
fi
if [ ! -w "$dest_dir" ]; then
echo "ERROR: No write permission for: $dest_dir" >&2
return 1
fi
# Check if destination already exists
if [ -e "$dest" ]; then
echo "WARNING: Destination already exists: $dest" >&2
# Check write permission
if [ ! -w "$dest" ]; then
echo "ERROR: Cannot overwrite $dest (no write permission)" >&2
return 1
fi
# Prompt user
echo -n "Overwrite? (y/n): "
read -r answer
if [[ ! "$answer" =~ ^[Yy]$ ]]; then
echo "Move cancelled by user" >&2
return 1
fi
fi
# Attempt move
if mv "$src" "$dest" 2>/dev/null; then
echo "Successfully moved: $src -> $dest"
return 0
else
echo "ERROR: Move failed" >&2
return 1
fi
}
# Batch move with error collection
batch_move() {
local dest_dir="$1"
shift
local errors=()
local success=0
local failed=0
mkdir -p "$dest_dir" 2>/dev/null
for src in "$@"; do
if robust_move "$src" "$dest_dir/"; then
success=$((success + 1))
else
failed=$((failed + 1))
errors+=("$src")
fi
done
echo "=== Move Summary ==="
echo "Successful: $success"
echo "Failed: $failed"
if [ ${#errors[@]} -gt 0 ]; then
echo "Failed files:"
printf ' %s\n' "${errors[@]}"
fi
}
Recovery Strategies
#!/bin/bash
# Undo last move operation
undo_last_move() {
local logfile="${1:-/tmp/mv_history.log}"
if [ ! -f "$logfile" ]; then
echo "No move history found"
return 1
fi
# Get last move from log
local last_move=$(tail -n 1 "$logfile")
if [ -z "$last_move" ]; then
echo "No moves in history"
return 1
fi
# Parse source and destination
local src=$(echo "$last_move" | cut -d'|' -f1)
local dest=$(echo "$last_move" | cut -d'|' -f2)
echo "Undoing: $src -> $dest"
if [ -e "$dest" ]; then
mv "$dest" "$src"
echo "Undo successful"
# Remove last line from log
sed -i '$d' "$logfile"
else
echo "Cannot undo: destination no longer exists"
return 1
fi
}
# Recover from failed move
recover_move() {
local src="$1"
local dest="$2"
local temp_dir="/tmp/mv_recovery_$$"
# Create recovery directory
mkdir -p "$temp_dir"
# Try to copy first, then move
if cp -a "$src" "$temp_dir/" 2>/dev/null; then
echo "Backup created in $temp_dir"
if mv "$src" "$dest" 2>/dev/null; then
echo "Move successful"
rm -rf "$temp_dir"
return 0
else
echo "Move failed, restoring from backup"
mv "$temp_dir/$(basename "$src")" "$src"
return 1
fi
else
echo "Could not create backup"
return 1
fi
}
# Journal-based move system
journal_move() {
local src="$1"
local dest="$2"
local journal="/tmp/mv_journal.log"
# Create journal entry
echo "$(date +%s)|$src|$dest|pending" >> "$journal"
local entry_id=$(wc -l < "$journal")
# Perform move
if mv "$src" "$dest" 2>/dev/null; then
# Update journal
sed -i "${entry_id}s/pending/completed/" "$journal"
echo "Move completed successfully"
return 0
else
# Update journal
sed -i "${entry_id}s/pending/failed/" "$journal"
echo "Move failed"
return 1
fi
}
6. Integration with Other Commands
With find
#!/bin/bash
# Move files older than X days
find /path/to/source -type f -mtime +30 -exec mv {} /path/to/archive/ \;
# Move files by extension
find . -name "*.tmp" -exec mv {} /tmp/ \;
# Move empty directories
find . -type d -empty -exec mv {} /path/to/empty/ \;
# Move files modified today
find . -type f -mtime 0 -exec mv {} /path/to/today/ \;
# Move files by size
find . -type f -size +10M -exec mv {} /path/to/large/ \;
# Move and rename with find
find . -name "*.bak" -exec sh -c '
for file; do
newname="${file%.bak}.old"
mv "$file" "$newname"
done
' _ {} +
# Move with confirmation
find . -name "*.log" -ok mv {} /archive/ \;
With rsync
#!/bin/bash
# Use rsync for reliable moves (copy then delete)
reliable_move() {
local src="$1"
local dest="$2"
# Use rsync to copy
if rsync -av --progress "$src" "$dest"; then
echo "Copy successful, removing source..."
rm -rf "$src"
else
echo "Copy failed, source preserved"
return 1
fi
}
# Move with verification
verified_move() {
local src="$1"
local dest="$2"
# Use rsync with checksum
rsync -ac --progress "$src" "$dest"
# Verify and delete
if [ $? -eq 0 ]; then
echo "Verification passed, removing source"
rm -rf "$src"
else
echo "Verification failed, source preserved"
return 1
fi
}
# Move large directories with progress
move_large_dir() {
local src="$1"
local dest="$2"
echo "Moving $src to $dest"
echo "Estimating size..."
# Use rsync for progress
rsync -av --progress "$src" "$dest" | pv -l -s $(find "$src" -type f | wc -l) > /dev/null
if [ $? -eq 0 ]; then
echo "Copy complete, verifying..."
# Quick verification with file count
src_count=$(find "$src" -type f | wc -l)
dest_count=$(find "$dest" -type f | wc -l)
if [ "$src_count" -eq "$dest_count" ]; then
echo "Verification passed, removing source"
rm -rf "$src"
else
echo "Verification failed: file count mismatch"
return 1
fi
else
echo "Copy failed"
return 1
fi
}
With tar for Archival Moves
#!/bin/bash
# Move files by archiving first
archive_move() {
local src_dir="$1"
local dest_dir="$2"
local archive_name="move_archive_$(date +%Y%m%d_%H%M%S).tar"
# Create archive
tar -cf "$archive_name" -C "$(dirname "$src_dir")" "$(basename "$src_dir")"
# Extract at destination
if tar -xf "$archive_name" -C "$dest_dir"; then
echo "Extraction successful, removing source"
rm -rf "$src_dir" "$archive_name"
else
echo "Extraction failed, preserving source and archive"
return 1
fi
}
# Compress during move
compress_and_move() {
local src="$1"
local dest="${2%.*}.tar.gz"
tar -czf "$dest" -C "$(dirname "$src")" "$(basename "$src")" && rm -rf "$src"
echo "Moved and compressed to: $dest"
}
7. Performance Optimization
Large File Handling
#!/bin/bash
# Split and move large files
split_and_move() {
local large_file="$1"
local dest_dir="$2"
local chunk_size="${3:-100M}"
local temp_dir="/tmp/split_$$"
mkdir -p "$temp_dir"
# Split file
split -b "$chunk_size" "$large_file" "$temp_dir/part_"
# Move parts
mv "$temp_dir"/* "$dest_dir/"
# Recreate at destination (use cat to reassemble)
echo "To reassemble: cat part_* > $large_file"
# Remove original
rm -f "$large_file"
rmdir "$temp_dir"
}
# Move with progress bar for large files
move_with_progress() {
local src="$1"
local dest="$2"
if [ -f "$src" ]; then
local size=$(stat -c%s "$src" 2>/dev/null || stat -f%z "$src" 2>/dev/null)
pv -s "$size" "$src" > "$dest" && rm "$src"
else
echo "Not a regular file: $src"
return 1
fi
}
# Parallel chunk mover
parallel_chunk_move() {
local src_dir="$1"
local dest_dir="$2"
local num_chunks="${3:-4}"
find "$src_dir" -type f | split -l $(( $(find "$src_dir" -type f | wc -l) / num_chunks + 1 )) - chunk_list_
for chunk in chunk_list_*; do
(
while IFS= read -r file; do
rel_path="${file#$src_dir/}"
mkdir -p "$dest_dir/$(dirname "$rel_path")"
mv "$file" "$dest_dir/$rel_path"
done < "$chunk"
) &
done
wait
rm -f chunk_list_*
rmdir "$src_dir" 2>/dev/null
}
8. Special Cases
Handling Special Characters
#!/bin/bash
# Handle files with spaces, newlines, special characters
safe_mv() {
local src="$1"
local dest="$2"
# Use null separator for safety
printf '%s\0' "$src" | xargs -0 mv -t "$dest"
}
# Move files with problematic names
cleanup_filenames() {
for file in *; do
if [ -f "$file" ]; then
# Remove control characters
newname=$(echo "$file" | tr -d '[:cntrl:]')
# Replace problematic characters
newname=$(echo "$newname" | sed 's/[^-_./a-zA-Z0-9]/_/g')
if [ "$newname" != "$file" ]; then
mv -v "$file" "$newname"
fi
fi
done
}
# Handle symlinks properly
move_symlink() {
local link="$1"
local dest="$2"
if [ -L "$link" ]; then
# Preserve symlink target
local target=$(readlink "$link")
mv "$link" "$dest"
ln -s "$target" "$dest/$(basename "$link")"
else
mv "$link" "$dest"
fi
}
Handling Device Files and Special Files
#!/bin/bash
# Move device files (requires root)
move_device_file() {
local device="$1"
local dest="$2"
if [ -c "$device" ] || [ -b "$device" ]; then
# Character or block device
sudo mv "$device" "$dest"
else
echo "Not a device file"
return 1
fi
}
# Move with preservation of special attributes
move_with_attrs() {
local src="$1"
local dest="$2"
# Preserve extended attributes
cp -a "$src" "$dest" && rm -rf "$src"
}
# Handle named pipes (FIFO)
move_fifo() {
local fifo="$1"
local dest="$2"
if [ -p "$fifo" ]; then
mv "$fifo" "$dest"
fi
}
9. Scripting Patterns
Configuration File Management
#!/bin/bash
# Safe configuration file replacement
replace_config() {
local new_config="$1"
local target_config="$2"
local backup="${target_config}.backup"
# Create backup
if [ -f "$target_config" ]; then
cp "$target_config" "$backup"
echo "Backup created: $backup"
fi
# Move new config into place
if mv "$new_config" "$target_config"; then
echo "Configuration updated"
# Test configuration
if ! test_config "$target_config"; then
echo "Configuration test failed, restoring backup"
mv "$backup" "$target_config"
return 1
fi
else
echo "Failed to update configuration"
return 1
fi
}
# Rotate configuration files
rotate_configs() {
local config_dir="$1"
local max_backups="${2:-5}"
cd "$config_dir" || exit 1
# Remove oldest backup
if [ -f "config.${max_backups}" ]; then
rm -f "config.${max_backups}"
fi
# Rotate backups
for i in $(seq $((max_backups - 1)) -1 1); do
if [ -f "config.$i" ]; then
mv "config.$i" "config.$((i + 1))"
fi
done
# Move current config
if [ -f "config" ]; then
mv "config" "config.1"
fi
}
Log Rotation
#!/bin/bash
# Simple log rotation
rotate_log() {
local logfile="$1"
local max_size="${2:-10485760}" # Default 10MB
if [ -f "$logfile" ]; then
local size=$(stat -c%s "$logfile" 2>/dev/null || stat -f%z "$logfile" 2>/dev/null)
if [ "$size" -gt "$max_size" ]; then
timestamp=$(date +%Y%m%d-%H%M%S)
mv "$logfile" "${logfile}.${timestamp}"
touch "$logfile"
echo "Log rotated: ${logfile}.${timestamp}"
# Clean old logs
find "$(dirname "$logfile")" -name "$(basename "$logfile").*" -mtime +30 -delete
fi
fi
}
# Date-based log rotation
daily_rotate() {
local logfile="$1"
if [ -f "$logfile" ]; then
yesterday=$(date -d "yesterday" +%Y%m%d)
mv "$logfile" "${logfile}.${yesterday}"
touch "$logfile"
fi
}
10. Monitoring and Logging
Move Operations Monitoring
#!/bin/bash
# Monitor move operations
monitor_moves() {
local watch_dir="$1"
inotifywait -m "$watch_dir" -e moved_from,moved_to |
while read -r directory events filename; do
echo "$(date '+%Y-%m-%d %H:%M:%S'): $events $filename in $directory"
done
}
# Log all moves
log_moves() {
local src="$1"
local dest="$2"
local logfile="${3:-/var/log/file_moves.log}"
{
echo "=== Move Operation ==="
echo "Timestamp: $(date '+%Y-%m-%d %H:%M:%S')"
echo "User: $(whoami)"
echo "Source: $src"
echo "Destination: $dest"
echo "Size: $(du -h "$src" 2>/dev/null | cut -f1)"
echo "==================="
} >> "$logfile"
mv "$src" "$dest" && echo "Move logged" || echo "Move failed"
}
# Audit trail for moves
audit_move() {
local src="$1"
local dest="$2"
local audit_file="/var/log/mv_audit.log"
# Record before state
before_hash=$(md5sum "$src" 2>/dev/null | cut -d' ' -f1)
before_size=$(stat -c%s "$src" 2>/dev/null || stat -f%z "$src" 2>/dev/null)
# Perform move
mv "$src" "$dest"
local mv_status=$?
# Record after state
after_hash=$(md5sum "$dest" 2>/dev/null | cut -d' ' -f1)
after_size=$(stat -c%s "$dest" 2>/dev/null || stat -f%z "$dest" 2>/dev/null)
# Log audit information
cat >> "$audit_file" << EOF
[$(date '+%Y-%m-%d %H:%M:%S')]
Operation: MOVE
Source: $src
Destination: $dest
Status: $([ $mv_status -eq 0 ] && echo "SUCCESS" || echo "FAILED")
Before: hash=$before_hash, size=$before_size
After: hash=$after_hash, size=$after_size
User: $(whoami)
PID: $$
---
EOF
}
11. Network and Remote Operations
Remote Moves with SSH
#!/bin/bash
# Move file to remote server
remote_move() {
local src="$1"
local user="$2"
local host="$3"
local dest="$4"
# Copy to remote
scp "$src" "${user}@${host}:${dest}" && rm "$src"
}
# Move with rsync over SSH
ssh_move() {
local src="$1"
local dest="$2" # user@host:/path
rsync -avz --remove-source-files -e ssh "$src" "$dest"
}
# Synchronized move (ensure network operation)
network_move() {
local src="$1"
local dest="$2"
local retries=3
for i in $(seq 1 $retries); do
if rsync -avz --partial --progress "$src" "$dest"; then
rm -rf "$src"
echo "Move successful after $i attempt(s)"
return 0
else
echo "Attempt $i failed, retrying..."
sleep 5
fi
done
echo "Move failed after $retries attempts"
return 1
}
12. Best Practices and Tips
Safety Guidelines
#!/bin/bash # Always use double quotes for variables mv "$source" "$destination" # Good mv $source $destination # Bad - breaks with spaces # Check source existence before move [ -e "$source" ] && mv "$source" "$dest" # Create destination directory if needed mkdir -p "$(dirname "$dest")" && mv "$src" "$dest" # Use -i for interactive operations mv -i "$src" "$dest" # Use -n to prevent overwrites mv -n "$src" "$dest" # Test move operations first with echo echo mv "$src" "$dest" # Use verbose mode for confirmation mv -v "$src" "$dest" # Handle errors explicitly if ! mv "$src" "$dest"; then echo "Move failed" exit 1 fi
Performance Tips
#!/bin/bash
# Use same filesystem moves when possible (instant)
# Different filesystem moves require copy+delete (slow)
# Check if source and dest are on same device
same_device() {
local src="$1"
local dest="$2"
src_dev=$(stat -c "%d" "$src" 2>/dev/null || stat -f "%d" "$src" 2>/dev/null)
dest_dev=$(stat -c "%d" "$(dirname "$dest")" 2>/dev/null || stat -f "%d" "$(dirname "$dest")" 2>/dev/null)
[ "$src_dev" = "$dest_dev" ]
}
# Optimize move based on device
smart_move() {
if same_device "$1" "$2"; then
mv "$1" "$2" # Fast atomic move
else
rsync -av --remove-source-files "$1" "$2" # Progress with verification
fi
}
# Use mv for single files, rsync for directories
if [ -f "$src" ]; then
mv "$src" "$dest"
elif [ -d "$src" ]; then
rsync -av --remove-source-files "$src/" "$dest/"
rmdir "$src"
fi
Conclusion
The mv command is a powerful tool for file system management:
Key Takeaways
- Dual Purpose: Both moving and renaming files/directories
- Options Matter: -i (safe), -f (force), -n (no overwrite), -v (verbose)
- Atomic Operations: Moves within same filesystem are atomic
- Cross-Filesystem: Actually copy+delete, can be slow for large files
- Safety First: Use -i for interactive, -n for no-overwrite
- Script Integration: Essential for file management automation
Common Options Summary
| Option | Name | Description |
|---|---|---|
-i | Interactive | Prompt before overwrite |
-f | Force | Overwrite without prompt |
-n | No-overwrite | Never overwrite existing files |
-v | Verbose | Show what's being done |
-u | Update | Move only newer files |
Best Practices Checklist
- [ ] Always quote variables with spaces
- [ ] Use
-ifor interactive moves - [ ] Check source exists before moving
- [ ] Verify destination directory is writable
- [ ] Consider using
-nin scripts to prevent overwrites - [ ] Test with
echofirst for complex moves - [ ] Log important move operations
- [ ] Handle errors explicitly
- [ ] Use same filesystem moves for performance
- [ ] Create backups before overwriting important files
The mv command, while simple, requires careful usage to avoid data loss. Understanding its behavior and options is essential for effective system administration and scripting.