#!/usr/bin/env bash declare -r esc=$'\033' declare -r c_reset="${esc}[0m" declare -r c_red="${esc}[31m" declare dryrun verbose set -e err() { printf "${c_red}%s${c_reset}\n" "$*" >&2 } die() { exit 1 } has() { local verbose=0 if [[ $1 == '-v' ]]; then verbose=1 shift fi for c; do c="${c%% *}" if ! command -v "$c" &> /dev/null; then (( verbose > 0 )) && err "$c not found" return 1 fi done } has -v fzf || die fzf() { command fzf --cycle "$@" } pick_files() { local files fzpick find . -maxdepth 1 2> /dev/null | sort -h | sed '1d; s|^\./||' | while read -r f; do if [[ -d "$f" ]]; then printf '%s/\n' "$f" elif [[ -L "$f" ]]; then printf '%s@\n' "$f" else printf '%s\n' "$f" fi done | fzf --multi --header='move these files' || return 1 } pick_destination() { local cwd browse_dir browse_info query dirs cwd=$(pwd) while [[ "$browse_dir" != "$cwd" ]]; do mapfile -t browse_info < <( { echo '..'; find . -maxdepth 1 -type d 2> /dev/null; } | sed 's|^./||' | sort -h | fzf --print-query \ --history="${HOME}/.cache/fzmv_history" \ --header="${errors:-move files here}") query=${browse_info[0]} browse_dir=${browse_info[1]} files=( "${browse_info[@]:2}" ) [[ -d "$query" ]] && browse_dir="$query" [[ ! -d "$browse_dir" ]] && return 1 if [[ "$browse_dir" == '.' && $(realpath "$browse_dir") != "$cwd" ]]; then realpath "$browse_dir" break else cd "$browse_dir" || die continue fi done } while (( $# > 0 )); do case $1 in -t|--test) dryrun=true ;; -v|--verbose) verbose=1 ;; esac shift done mapfile -t files < <(pick_files) (( ${#files[@]} > 0 )) || exit 1 destination=$(pick_destination) || exit 1 ${dryrun:+echo} mv ${verbose:+-v} -t "$destination" "${files[@]}"