September 16

13 небольших Bash-скриптов, которые ежедневно экономят время

Это перевод оригинальной статьи 13 Tiny Bash Scripts That Save Me Time Every Day.

Подписывайтесь на телеграм-канал usr_bin, где я публикую много полезного по Linux, в том числе ссылки на статьи в этом блоге.

Простые shell-скрипты, которые можно использовать каждый день и которые повысят производительность.

Вам не нужен Bash-скрипт на 500 строк.

Когда люди думают о shell-скриптах, они часто представляют себе огромные скрипты с множеством функций, переменных и сложной логикой, которые выполняют большие задачи. В результате многие новички считают, что shell-скрипты слишком сложны для них, и продолжают вводить команды по одной.

Shell-скрипты не обязаны быть сложными. Простой скрипт на 10 строк, который избавляет нас от необходимости каждый день вводить одни и те же команды, может быть невероятно полезным. Он экономит время, уменьшает количество ошибок, сокращает объем повторяющейся работы и делает наши повседневные задачи проще и эффективнее.

В этой статье мы рассмотрим практические Bash-скрипты, которые короткие, простые для понимания и решают распространенные повседневные задачи.

#1. Создавайте резервную копию перед редактированием файлов

Одно из золотых правил системного администрирования — всегда создавать резервные копии перед редактированием важного файла. Независимо от того, редактируете ли вы /etc/nginx/nginx.conf, /etc/ssh/sshd_config или файл .env, наличие резервной копии позволяет быстро восстановить оригинал, если что-то пойдет не так.

Конечно, cp file.conf file.conf.bak работает идеально. Но вводить одну и ту же команду каждый раз при редактировании файла становится утомительно. Небольшой Bash-скрипт может сделать то же самое с помощью более короткой и простой команды.

#!/usr/bin/env bash
# bak - create a timestamped backup of a file
# Usage: bak <file> [file ...]

set -euo pipefail

if [[ $# -eq 0 ]]; then
    echo "Usage: bak <file> [file ...]" >&2
    exit 1
fi

TIMESTAMP=$(date +%Y%m%d-%H%M%S)

for file in "$@"; do
    if [[ ! -e "$file" ]]; then
        echo "bak: '$file' not found" >&2
        continue
    fi

    backup="${file}.${TIMESTAMP}.bak"

    cp -a -- "$file" "$backup"
    echo "Created backup: $backup"
done
#Usage:
bak /etc/nginx/nginx.conf
# Created backup: /etc/nginx/nginx.conf.20260618-143015.bak

bak ~/.bashrc ~/.vimrc    # Multiple input
# Created backup: /home/user/.bashrc.20260618-143027.bak
# Created backup: /home/user/.vimrc.20260618-143027.bak    

Вместо того чтобы дважды вводить длинные пути в команде cp file.conf file.conf.bak, этот скрипт автоматизирует процесс и добавляет точную временную метку, чтобы избежать перезаписи предыдущих резервных копий.

Примечание: Чтобы использовать bak как команду, убедитесь, что скрипт находится в каталоге, который входит в PATH (например, ~/bin), сделайте его исполняемым с помощью chmod +x и перезагрузите конфигурацию shell (source ~/.bashrc).

$PATH: Переменная, управляющая всей вашей цепочкой инструментов

Перестаньте быть обычным пользователем! Освойте $PATH и станьте продвинутым пользователем.

medium.com

#2. Создавайте каталог и сразу переходите в него

Создание новой директории и последующий переход в неё — это то, что многие из нас делают несколько раз в день, особенно при начале нового проекта или организации файлов.

Мы всегда можем выполнить mkdir -p complex/path/to/project, затем cd complex/path/to/project, или объединить их в одну команду: mkdir -p complex/path/to/project && cd complex/path/to/project. Это работает идеально, но каждый раз вводить одну и ту же длинную команду — больше усилий, чем должно требоваться для такой распространенной задачи.

# Add to ~/.bashrc - not a standalone script, needs to be a function
mkcd() {
    if [ -z "$1" ]; then
        echo "Usage: mkcd <directory_path>"
        return 1
    fi

    mkdir -p "$1" && cd "$1"
}
# Usage:
mkcd my-project                
mkcd projects/rust/axum-demo  # Create nested directories if they don't already exist
mkcd "My New Project"         # Use quotes for directory names that contain spaces

Благодаря mkcd создание каталога и переход в него становится операцией в один шаг, что экономит несколько нажатий клавиш каждый раз, когда мы начинаем новый проект.

Примечание: Отдельный Bash-скрипт не может изменить текущий рабочий каталог терминала, который его запускает. Любая команда cd внутри скрипта влияет только на сам скрипт, и после его завершения терминал остается в исходном каталоге. Именно поэтому здесь используется функция shell.

#3. Распаковывайте любой архив

Сжатые файлы бывают самых разных форматов, таких как .zip, .tar.gz, .tgz, .tar.bz2 и .tar.xz. Проблема в том, что для каждого формата существует своя команда распаковки, которую бывает трудно запомнить.

#!/usr/bin/env bash
# extract — universal archive extractor
# Usage: extract <file> [file ...]
set -euo pipefail

if [[ $# -eq 0 ]]; then
    echo "Usage: extract <archive> [archive ...]" >&2
    exit 1
fi

for file in "$@"; do
    if [[ ! -f "$file" ]]; then
        echo "extract: '$file' not found" >&2
        continue
    fi
    echo "Extracting: $file"
    case "$file" in
        *.tar.bz2|*.tbz2)  tar xjf "$file"     ;;
        *.tar.gz|*.tgz)    tar xzf "$file"     ;;
        *.tar.xz|*.txz)    tar xJf "$file"     ;;
        *.tar.zst)         tar --zstd -xf "$file" ;;
        *.tar)             tar xf "$file"      ;;
        *.bz2)             bunzip2 "$file"     ;;
        *.gz)              gunzip "$file"      ;;
        *.xz)              unxz "$file"        ;;
        *.zip)             unzip "$file"       ;;
        *.7z)              7z x "$file"        ;;
        *.rar)             unrar x "$file"     ;;
        *.Z)               uncompress "$file"  ;;
        *.deb)
            mkdir -p "${file%.deb}"
            dpkg-deb -xv "$file" "${file%.deb}"
            ;;
        *.rpm)
            mkdir -p "${file%.rpm}"
            cd "${file%.rpm}" && rpm2cpio "../$file" | cpio -idmv
            cd ..
            ;;
        *)
            echo "extract: unknown format — '$file'" >&2
            ;;
    esac
done
# Usage:
extract archive.tar.gz
extract file1.zip file2.tar.bz2 file3.7z   # multiple at once

Вместо того чтобы запоминать отдельную команду для каждого формата архива, нам достаточно запомнить только одну: extract <filename>. Скрипт автоматически определяет тип файла и запускает правильную команду распаковки. Он поддерживает практически все форматы архивов, с которыми мы сталкиваемся в повседневной работе.

Примечание:
Для некоторых форматов архивов требуются дополнительные утилиты. Например, для распаковки файлов .rar необходим unrar, а для распаковки .7zp7zip (или p7zip-full в Debian/Ubuntu).

#4. Сжимайте файлы и каталоги удобным способом

Создание архивов так же распространено, как и их распаковка. Создаем ли мы резервную копию проекта, делимся файлами или архивируем логи — нам часто приходится запоминать разные команды в зависимости от того, что именно мы сжимаем и какой формат архива хотим получить.

#!/usr/bin/env bash
# compress — compress files or directories
# Usage: compress <target> [format]
# Formats: gz (default), bz2, xz, zip, zst
set -euo pipefail

TARGET="${1:-}"
FORMAT="${2:-gz}"
if [[ -z "$TARGET" ]]; then
    echo "Usage: compress <file-or-dir> [gz|bz2|xz|zip|zst]" >&2
    exit 1
fi
if [[ ! -e "$TARGET" ]]; then
    echo "compress: '$TARGET' not found" >&2
    exit 1
fi
# Strip trailing slash
TARGET="${TARGET%/}"
NAME=$(basename "$TARGET")
STAMP=$(date +"%Y%m%d_%H%M%S")
case "$FORMAT" in
    gz)
        OUTPUT="${NAME}_${STAMP}.tar.gz"
        tar czf "$OUTPUT" "$TARGET"
        ;;
    bz2)
        OUTPUT="${NAME}_${STAMP}.tar.bz2"
        tar cjf "$OUTPUT" "$TARGET"
        ;;
    xz)
        OUTPUT="${NAME}_${STAMP}.tar.xz"
        tar cJf "$OUTPUT" "$TARGET"
        ;;
    zst)
        OUTPUT="${NAME}_${STAMP}.tar.zst"
        tar --zstd -cf "$OUTPUT" "$TARGET"
        ;;
    zip)
        OUTPUT="${NAME}_${STAMP}.zip"
        zip -r "$OUTPUT" "$TARGET"
        ;;
    *)
        echo "compress: unknown format '$FORMAT'. Use: gz bz2 xz zst zip" >&2
        exit 1
        ;;
esac
SIZE=$(du -sh "$OUTPUT" | cut -f1)
echo "  Created: $OUTPUT ($SIZE)"
compress ./my-project              # → my-project_20240617_143022.tar.gz
compress ./logs bz2                # → logs_20240617_143022.tar.bz2
compress config.yml zip            # → config.yml_20240617_143022.zip

Этот скрипт позволяет сжимать файлы или каталоги в формате по умолчанию либо выбрать другой формат, указав его в качестве аргумента. Нам больше не нужно запоминать команды вроде tar -czf или zip -r.

Временная метка в имени архива предотвращает перезапись предыдущих архивов, что делает этот подход идеальным для быстрого создания снимков перед важными изменениями.

#5. Быстрый поиск больших файлов

Со временем дисковое пространство заполняется по мере накопления больших файлов — старые резервные копии, логи, образы виртуальных машин и архивы могут незаметно занять гигабайты.

Один из самых быстрых способов понять, в чем проблема, — использовать find, чтобы определить, что именно занимает место в дереве каталогов. Но синтаксис этой команды не всегда легко запомнить.

#!/usr/bin/env bash
# findlarge — find the largest files under a path
# Usage: findlarge [directory] [min_size_MB] [count]
set -euo pipefail

DIR="${1:-.}"
MIN_MB="${2:-100}"
COUNT="${3:-20}"
echo "Finding files larger than ${MIN_MB}MB under ${DIR} ..."
echo ""
find "$DIR" -type f -size +"${MIN_MB}M" -printf '%s\t%p\n' 2>/dev/null \
    | sort -rn \
    | head -"$COUNT" \
    | awk '{
        size=$1
        path=$2
        if (size >= 1073741824)
            printf "  %7.2f GB  %s\n", size/1073741824, path
        else
            printf "  %7.2f MB  %s\n", size/1048576, path
    }'
# Usage: 
findlarge /var 50 10      # top 10 files >50MB under /var
findlarge /home 200       # default top 20, >200MB, under /home
findlarge                 # top 20 files >100MB in current directory

Этот скрипт позволяет легко находить большие файлы при очистке дискового пространства или поиске того, что занимает место на диске. Использование параметра -printf команды find выводит размеры файлов в байтах, что удобно для сортировки и надежнее, чем разбирать размеры в человекочитаемом формате.

#6. Краткий обзор состояния системы

Первое, что нам нужно выполнить на любой незнакомой машине или в любой момент, когда что-то работает не так, — это проверить её текущее состояние: время работы, среднюю нагрузку, давление на память, использование диска и процессы с наибольшим потреблением CPU, объединив все это в единый, легко читаемый отчет.

Запуск каждой команды отдельно работает, но это занимает время и требует помнить несколько команд.

#!/usr/bin/env bash
# sysbrief — quick system health snapshot
set -euo pipefail

BOLD='\033[1m'
RESET='\033[0m'
RED='\033[0;31m'
YELLOW='\033[0;33m'
GREEN='\033[0;32m'
section() { echo -e "\n${BOLD}── $1 ──${RESET}"; }
section "System Information"
echo "Host   : $(hostname)"
echo "Kernel : $(uname -r)"
section "Uptime & Load"
uptime -p
awk '{printf "Load avg: %.2f %.2f %.2f\n", $1, $2, $3}' /proc/loadavg
section "Memory"
free -h | awk 'NR==2 {
    used=$3; total=$2; avail=$7
    printf "Used: %s / %s  |  Available: %s\n", used, total, avail
}'
section "Disk Usage (>70%)"
df -h --output=target,pcent,size,avail | awk 'NR==1 || $2+0 > 70 {
    if (NR==1) print; else print "\033[0;33m" $0 "\033[0m"
}'
section "Top 5 CPU Consumers"
ps -eo pid,comm,%cpu,%mem --sort=-%cpu | head -6
section "Swap"
swapon --show 2>/dev/null || echo "No swap configured"
sysbrief

Вместо запуска нескольких команд, таких как uptime, df -h и free -h, нам нужна только одна команда, чтобы получить быстрый обзор нашей системы. Она особенно полезна при устранении проблем или проверке состояния сервера.

How I Troubleshoot Linux in Production Without Taking Servers Down

Перестаньте перезагружаться. Вот что следует проверить в первую очередь.

medium.com

#7. Удаление дубликатов файлов в каталоге

Наличие дубликатов файлов — обычное явление, особенно после многократной загрузки одного и того же файла, копирования папок проекта или объединения резервных копий. Со временем они занимают место на диске и усложняют организацию каталогов.

Поиск дубликатов вручную выполняется медленно и часто приводит к ошибкам, особенно в больших каталогах, где многие файлы могут иметь похожие имена или содержимое.

#!/usr/bin/env bash
# rmdup — find (and optionally remove) duplicate files
# Usage: rmdup [directory] [--delete]
set -euo pipefail

DIR="${1:-.}"
DELETE="${2:-}"
echo "Scanning for duplicates in: $DIR"
echo ""
declare -A SEEN
DUPES=()
while IFS= read -r -d '' file; do
    hash=$(md5sum "$file" | cut -d' ' -f1)
    if [[ -n "${SEEN[$hash]:-}" ]]; then
        DUPES+=("$file")
        echo "  DUP  $file"
        echo "  OF   ${SEEN[$hash]}"
        echo ""
    else
        SEEN[$hash]="$file"
    fi
done < <(find "$DIR" -maxdepth 1 -type f -print0 | sort -z)
if [[ ${#DUPES[@]} -eq 0 ]]; then
    echo "No duplicates found."
    exit 0
fi
echo "Found ${#DUPES[@]} duplicate(s)."
if [[ "$DELETE" == "--delete" ]]; then
    for f in "${DUPES[@]}"; do
        rm -v "$f"
    done
    echo "Deleted ${#DUPES[@]} duplicate(s)."
else
    echo "Run with --delete to remove duplicates."
fi
# Usage:
rmdup ~/Downloads              # show duplicates only
rmdup ~/Downloads --delete     # show and delete duplicates

Вместо ручного сравнения файлов rmdup сравнивает контрольную сумму каждого файла, чтобы найти файлы с одинаковым содержимым. Он явно показывает, какой файл является дубликатом другого, что позволяет легко проверить результаты перед выполнением каких-либо действий.

По умолчанию он только показывает список дубликатов, а для их удаления необходимо использовать --delete, что делает запуск более безопасным.

#8. Мониторинг нескольких сервисов

При устранении проблем в системе нам часто требуется одновременно отслеживать логи нескольких сервисов. Например, мы можем захотеть наблюдать логи Nginx, Docker и нашего приложения.

Запуск отдельных команд journalctl -f в разных терминалах работает, но быстрое переключение между окнами становится неудобным.

#!/usr/bin/env bash
# journalwatch — monitor multiple services with color-coded output
# Usage: journalwatch nginx [...]
set -euo pipefail

services=("$@")

if [ $# -eq 0 ]; then
    echo "Usage: journalwatch <service> [service ...]"
    exit 1
fi

colors=(
    "\033[31m"
    "\033[32m"
    "\033[33m"
    "\033[34m"
    "\033[35m"
)
reset="\033[0m"

for i in "${!services[@]}"; do
    service="${services[$i]}"
    color="${colors[$((i % ${#colors[@]}))]}"

    journalctl -fu "$service" |
    while IFS= read -r line; do
        printf "${color}[%s]${reset} %s\n" "$service" "$line"
    done &
done

wait
journalwatch nginx docker myapp

Вместо открытия нескольких терминалов мы можем отслеживать несколько сервисов systemd из одного окна. Цветные имена сервисов позволяют легко увидеть, какой сервис создал каждую запись в логах, что значительно упрощает устранение проблем.

Примечание:
Этот скрипт использует journalctl, поэтому он работает в Linux-системах, использующих systemd. Для просмотра логов некоторых сервисов может потребоваться sudo.

Journalctl: Diagnosing Linux issues the right way

LINUX | BASH | SYSADMIN | DEVOPS | PROGRAMMING Journalctl: Правильная диагностика проблем в Linux: перестаньте перезагружать свой…

blog.devops.dev

#9. Сводка Git-репозитория

При работе с Git-репозиторием мы часто выполняем несколько команд только для того, чтобы понять, что происходит. Мы можем посмотреть текущую ветку, проверить наличие незакоммиченных изменений, просмотреть последние коммиты или проверить, находится ли наша ветка впереди или позади удалённой ветки.

Запуск каждой команды отдельно работает, но быстро становится повторяющимся.

#!/usr/bin/env bash
# gitsum — git repo status summary
set -euo pipefail

BOLD='\033[1m'
CYAN='\033[0;36m'
YELLOW='\033[0;33m'
GREEN='\033[0;32m'
RESET='\033[0m'
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
    echo "Not inside a git repository." >&2
    exit 1
fi
ROOT=$(git rev-parse --show-toplevel)
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD)
REMOTE=$(git remote 2>/dev/null | head -1)
echo -e "${BOLD}Repository:${RESET} $(basename "$ROOT")"
echo -e "${BOLD}Branch:${RESET}     ${CYAN}${BRANCH}${RESET}"
# Ahead/behind tracking
if [[ -n "$REMOTE" ]]; then
    TRACKING=$(git for-each-ref --format='%(upstream:short)' "$(git symbolic-ref HEAD 2>/dev/null)" 2>/dev/null)
    if [[ -n "$TRACKING" ]]; then
        AHEAD=$(git rev-list "${TRACKING}..HEAD" --count 2>/dev/null || echo 0)
        BEHIND=$(git rev-list "HEAD..${TRACKING}" --count 2>/dev/null || echo 0)
        echo -e "${BOLD}Remote:${RESET}     ${REMOTE} (↑${AHEAD} ↓${BEHIND})"
    fi
fi
echo ""
echo -e "${BOLD}Working tree:${RESET}"
STATUS=$(git status --short)
if [[ -z "$STATUS" ]]; then
    echo -e "  ${GREEN}Clean${RESET}"
else
    echo "$STATUS" | sed 's/^/  /'
fi
echo ""
echo -e "${BOLD}Recent commits:${RESET}"
git log --oneline --graph --decorate -8
echo ""
echo -e "${BOLD}Stash:${RESET} $(git stash list 2>/dev/null | wc -l) entries"
gitsum

Вместо запуска git branch, git status, git log, git remote и git stash list отдельно мы можем использовать одну команду, чтобы увидеть текущую ветку, состояние рабочей директории, последние коммиты, количество записей в stash и находится ли ветка впереди или позади удалённой ветки. Это даёт быстрый обзор перед началом работы или отправкой изменений.

Git: Полное руководство для начинающих

Начало работы с Git. Узнайте, как настроить Git, создать коммит, работать с ветками Git и объединять ветки Git.

blog.devops.dev

#10. Проверка записей SSH-конфигурации

При управлении большим количеством удалённых хостов в ~/.ssh/config нам нужно убедиться, что записи доступны и что аутентификация по ключу работает.

Можно вручную пройтись по всем настроенным хостам и проверить их состояние, но это неудобно и занимает много времени.

#!/usr/bin/env bash
# sshcheck — test connectivity to all configured SSH hosts
# Usage: sshcheck [timeout_seconds]
set -euo pipefail

TIMEOUT="${1:-5}"
CONFIG="${HOME}/.ssh/config"
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[0;33m'
RESET='\033[0m'
if [[ ! -f "$CONFIG" ]]; then
    echo "No SSH config found at $CONFIG" >&2
    exit 1
fi
HOSTS=$(grep -i '^Host ' "$CONFIG" | awk '{print $2}' | grep -v '\*')
echo "Testing SSH hosts (timeout: ${TIMEOUT}s)..."
echo ""
printf "  %-30s %s\n" "Host" "Status"
printf "  %-30s %s\n" "----" "------"
while IFS= read -r host; do
    if ssh -o ConnectTimeout="$TIMEOUT" \
           -o BatchMode=yes \
           -o StrictHostKeyChecking=no \
           "$host" true 2>/dev/null; then
        printf "  ${GREEN}%-30s ✓ OK${RESET}\n" "$host"
    else
        EXIT=$?
        if [[ $EXIT -eq 255 ]]; then
            printf "  ${RED}%-30s ✗ Unreachable${RESET}\n" "$host"
        else
            printf "  ${YELLOW}%-30s ⚠ Auth failed (exit $EXIT)${RESET}\n" "$host"
        fi
    fi
done <<< "$HOSTS"
sshcheck       # test all hosts, 5s timeout
sshcheck 10    # 10s timeout

Вместо подключения к каждому серверу отдельно sshcheck проверяет каждый хост, определённый в ~/.ssh/config, с помощью одной команды. Он быстро показывает, какие серверы доступны, а какие требуют внимания, что делает его удобным инструментом перед развертыванием или регулярным обслуживанием.

SSH (Secure Shell): A Beginner’s Guide

Полное руководство по основам SSH, безопасным соединениям, управлению ключами и передовым методам современной разработки.

medium.com

#11. Проверка прослушиваемых портов

Одна из самых распространённых ежедневных задач разработчиков — определить, какой процесс владеет портом, особенно когда другой сервис уже использует его до того, как мы запускаем собственный.

Мы можем использовать ss или lsof для этого, но это всё ещё требует ручной проверки и не предоставляет быстрого обзора, из-за чего процесс становится медленным и повторяющимся.

#!/usr/bin/env bash
# portcheck — show listening ports with owning process
# Usage: portcheck [port]
set -euo pipefail

if [[ $# -eq 1 ]]; then
    ss -tlnp | awk -v port=":$1" '$4 ~ port || $5 ~ port'
    echo ""
    echo "Process detail:"
    lsof -i :"$1" -nP 2>/dev/null || echo "  (lsof unavailable or no match)"
else
    echo "All listening TCP/UDP ports:"
    echo ""
    ss -tulnp | column -t
fi
portcheck          # all listening ports
portcheck 5432     # who owns port 5432

Вместо запоминания разных команд ss и lsof нам нужно только выполнить portcheck. Он быстро показывает, какие порты прослушиваются и, для конкретного порта, какой процесс его использует. Это особенно полезно при проверке корректного запуска сервиса или исследовании конфликтов портов.

#12. Снимок сетевых подключений

При диагностике проблем с подключением или проверке исходящих подключений на сервере мы часто хотим быстро получить ответы: какие удалённые хосты подключены, сколько активных подключений существует и какие сервисы ожидают входящие подключения.

Хотя ss и netstat могут предоставить эту информацию, они выводят необработанные данные вместо понятной сводки.

#!/usr/bin/env bash
# netsnap — summarize active network connections
set -euo pipefail

BOLD='\033[1m'
RESET='\033[0m'
echo -e "${BOLD}Established connections by remote host:${RESET}"
ss -tnp state established \
    | awk 'NR>1 {print $4}' \
    | sed 's/:[0-9]*$//' \
    | sort | uniq -c | sort -rn \
    | awk '{printf "  %4d  %s\n", $1, $2}' \
    | head -20
echo ""
echo -e "${BOLD}Connection states summary:${RESET}"
ss -tan \
    | awk 'NR>1 {print $1}' \
    | sort | uniq -c | sort -rn \
    | awk '{printf "  %4d  %s\n", $1, $2}'
echo ""
echo -e "${BOLD}Listening services (TCP):${RESET}"
ss -tlnp \
    | awk 'NR>1 {printf "  %-25s %s\n", $4, $NF}'

Вместо ручного анализа вывода netsnap предоставляет быстрый обзор сетевой активности нашей системы одной командой. Это удобный способ увидеть активные подключения, состояния подключений и прослушиваемые сервисы при устранении проблем с сетью или приложениями.

Mastering Linux Networking

Изучите концепции, инструменты и настройки сети в Linux.

blog.devops.dev

How to Debug Linux Server Networking Issues

Практический контрольный список для ускорения отладки и снижения уровня разочарования.

medium.com

#13. Пакетное переименование файлов

Нередко бывает, что появляются десятки файлов с именами IMG_20240101_*.jpg, которые нужно переименовать в vacation_*.jpg, или требуется удалить пробелы из каждого имени файла в каталоге.

Переименование файлов вручную непрактично, когда нужно обновить десятки или сотни имён.

#!/usr/bin/env bash
# rename-batch — bulk rename files using find-and-replace on filenames
# Usage: rename-batch <find> <replace> [directory]
set -euo pipefail

FIND="${1:-}"
REPLACE="${2:-}"
DIR="${3:-.}"
if [[ -z "$FIND" || -z "$REPLACE" ]]; then
    echo "Usage: rename-batch <find> <replace> [directory]" >&2
    echo "  Replaces all occurrences of <find> in filenames under [directory]" >&2
    echo "  Example: rename-batch ' ' '_' ./downloads" >&2
    exit 1
fi
COUNT=0
while IFS= read -r -d '' file; do
    dir=$(dirname "$file")
    base=$(basename "$file")
    newbase="${base//"$FIND"/"$REPLACE"}"
    if [[ "$base" != "$newbase" ]]; then
        mv -- "$file" "$dir/$newbase"
        echo "  $base  →  $newbase"
        (( COUNT++ ))
    fi
done < <(find "$DIR" -maxdepth 1 -type f -print0 | sort -z)
echo ""
echo "Renamed $COUNT file(s)."
rename-batch ' ' '_' ~/Downloads        # spaces → underscores
rename-batch 'IMG_' 'photo_' ./         # prefix swap
rename-batch '.JPEG' '.jpg' ./photos    # extension normalization

Вместо редактирования каждого имени файла вручную мы можем применить операцию поиска и замены ко всем подходящим файлам. Это делает переименование быстрее, проще и безопаснее, поскольку оно затрагивает только файлы в выбранном каталоге.

Вывод: Простые скрипты, решающие повседневные проблемы, зачастую оказываются наиболее ценными.

Shell-скрипты не обязательно должны означать написание сотен строк кода. Многие самые полезные скрипты — это небольшие утилиты, которые решают повседневные задачи. Они устраняют повторяющуюся работу, уменьшают количество ошибок и позволяют сосредоточиться на настоящем решении проблем вместо повторения одних и тех же команд.

Когда вы сталкиваетесь с повторяющимися задачами, спросите себя: «Могу ли я превратить это в скрипт?» Со временем эти небольшие скрипты накапливаются — они экономят вам секунды здесь, предотвращают ошибки там и делают работу в терминале более удобной.

Скрипты в этой статье не предназначены быть идеальными или полностью функциональными. Думайте о них как о начальных вариантах, которые мы можем настроить под собственный сценарий работы.

Спасибо за чтение! Надеюсь, эта статья станет отправной точкой для создания собственных инструментов и повышения вашей продуктивности.

На этом все! Спасибо за внимание! Если статья была интересна, подпишитесь на телеграм-канал usr_bin, где будет еще больше полезной информации.