#!/bin/bash set -e _help() { echo "${HE}Console Clipboard${NO} Note: Copying happens only to a list. If source file is deleted, it can not be pasted anymore. Default clipboard is named: 0 List clipboard names: ${HL}$SELF l/list [clipboard]${NO} Make a link to clipboard: ${HL}$SELF c/copy filename[s] [clipboard]${NO} -n Use a new clipboard for every link Paste files using clipboard: ${HL}$SELF p/paste [switches] [clipboardname[s]] [folder/]${NO} Default folder: . Switches: -s Use soft link instead of file copying -l Use hardlink instead of file copying -k Keep clipboard link -m Move instead of copy (can not be used with any other switch) -r Use rsync to copy (verbose progress) Shortcut to switches example: ${HL}$SELF plk${NO} equals -l and -k Delete source files using clipboard links (WARNING): ${HL}$SELF rm/remove clipboardname[s]${NO} Edit links manually: ${HL}$SELF e/edit clipboardname[s]${NO} Clear clipboard links: ${HL}$SELF d/del clipboardname[s]${NO} Clear all clipboard links: ${HL}$SELF D/Del${NO} Get autocomplete: $SELF autocomplete Config: CCLIP_HOME environment variable sets clipboard storage folder, defauls to ~/.cache/cclip " exit } _shorthelp() { echo "${HE}Console Clipboard Short Help${NO} h/help/-h l/list [clipboard]${NO} c/copy filename[s] [clipboard]${NO} p/paste [switches] [clipboardname[s]] [folder/]${NO} rm/remove clipboardname[s]${NO} e/edit clipboardname[s]${NO} d/del clipboardname[s]${NO} D/Del${NO} " exit } _load_config() { # colors HL=$( _qCol W 2>/dev/null || true ) HE=$( _qCol Y U bk 2>/dev/null || true ) HERR=$( _qCol R bk 2>/dev/null || true ) NO=$( _qCol z 2>/dev/null || true ) SELF=$( basename "$0" ) STORAGE=${CCLIP_HOME:-~/.cache/cclip} [[ -d "$STORAGE" ]] || { echo "Creating $STORAGE folder" mkdir -p "$STORAGE" } } _list() { if [[ ! $( ls -A "${STORAGE}" ) ]]; then _err "No clipboard entries" return fi if [[ $# -gt 1 ]]; then list_only="${2}" fi # TODO: single clipboard only longest=4 for f in ${STORAGE}/*; do n=$( basename "$f" ) if [[ $longest -lt ${#n} ]]; then longest=${#n} fi done now=$( date -Idate ) printf "%s%-${longest}s %-10s %s%s\n" "$HE" Name Added/Size Path "$NO" IFS=$'\n' while read name; do if [[ -n "$list_only" ]]; then if [[ ! "$list_only" = "$name" ]]; then continue fi fi date=$( stat --printf "%y" "${STORAGE}/$name" ) if [[ "$date" = "$now"* ]]; then date=${date#* } date="${date:0:8}" else date=${date%% *} date="${date:2:8}" fi printf "%-${longest}s %-10s\n" \ "$name" \ "${date}" while read f; do if [[ ! -e "$f" ]]; then size=MISS"!" else size=$( du -k -h -s --apparent-size "$f" | awk '{ print $1 }' ) fi printf "%-${longest}s %5s %s%s%s\n" \ " " \ "$size" \ "$HL" "${f}" \ "$NO" done < "${STORAGE}/$name" done < <( ls "${STORAGE}" -t ) } _simple_list() { ls -1 "$STORAGE" } _copy() { numargs=$# if [[ $# -lt 2 ]]; then _help fi for (( i=2; i<=$numargs; i++ )); do case ${!i} in -n) AUTONUMBER=1 if [[ "$CLIPBOARD" -eq 0 ]]; then while [[ -e "$STORAGE/${CLIPBOARD}" ]]; do CLIPBOARD=$(( 1 + $CLIPBOARD )) done fi;; esac done if [[ -z "$AUTONUMBER" ]]; then for i in "$@"; do :;done if [[ ! -e "$i" ]]; then CLIPBOARD=$( echo "$i" | tr -d './' | tr -cd '[[:print:]]' ) if [[ -z "$CLIPBOARD" ]]; then _err "Invalid clipboard name: '$i'" exit 1 fi numargs=$(( numargs - 1 )) fi fi for (( i=2; i<=$numargs; i++ )); do case ${!i} in -n) continue; ;; esac name=$( basename "${!i}" ) origname="$name" path=$( readlink -f "${!i}" ) if [[ -d "$path" ]]; then path="$path/" fi printf "%s\n" "$path" >> "$STORAGE/${CLIPBOARD}" done sort -u -o "$STORAGE/${CLIPBOARD}" "$STORAGE/${CLIPBOARD}" _list l "$CLIPBOARD" } _edit() { numargs=$# if [[ $# -lt 2 ]]; then _help fi for (( i=2; i<=$numargs; i++ )); do CLIPBOARD=$( echo "${!i}" | tr -d './' | tr -cd '[[:print:]]' ) if [[ ! -f "$STORAGE/${!i}" ]]; then _err "No such clipboard: ${!i}" continue fi ${VISUAL:-vim} "$STORAGE/${CLIPBOARD}" _list l "$CLIPBOARD" done } _paste() { numargs=$# keeplink=0 # ps pl pk style command if [[ "$1" =~ s ]]; then softlink=1; fi if [[ "$1" =~ l ]]; then hardlink=1; fi if [[ "$1" =~ k ]]; then keeplink=1; fi if [[ "$1" =~ m ]]; then movelink=1; fi if [[ "$1" =~ r ]]; then usersync=1; fi # parse switches for (( i=2; i<=$#; i++ )); do case ${!i} in -s) softlink=1; ;; -l) hardlink=1; ;; -k) keeplink=1; ;; -m) movelink=1; ;; -r) usersync=1; ;; *) if [[ ! -f "$STORAGE/${!i}" ]]; then # no such clipboard if [[ $i -gt 2 ]]; then if [[ $i -eq $numargs ]]; then # but argument is last (but not only) OUTPUT=${!i} numargs=$(( numargs - 1 )) continue fi fi _err "No such clipboard: ${!i}" return fi ;; esac done echo "Pasting to folder: $OUTPUT" mkdir -p "$OUTPUT" for (( i=2; i<=$numargs; i++ )); do if [[ "${!i}" =~ ^-.$ ]]; then continue fi single_copied=1 _paste_clipboard "${!i}" done if [[ ! "$single_copied" = 1 ]]; then _paste_clipboard "$CLIPBOARD" fi } _paste_clipboard() { if [[ ! -f "$STORAGE/$1" ]];then _err "No such clipboard: '$1'" return fi while read f; do _paste_single "$f" done < "${STORAGE}/$1" if [[ "$movelink" -eq 1 ]]; then keeplink=0 fi if [[ "$softlink" -eq 1 ]]; then keeplink=1 fi if [[ "$keeplink" -eq 0 ]]; then echo "Removing clipboard: $1" rm "$STORAGE/$1" fi } _paste_single() { if [[ ! -e "$1" ]]; then _err "No such path: $1" return fi target=$( basename "$1" ) if [[ -d "$1" ]]; then extension="" basetarget="${target}" else if [[ "$target" = *.* ]]; then basetarget="${target%.*}" extension=."${target##*.}" else extension="" basetarget="${target}" fi fi origbase="$basetarget" post=0 while [[ -e "${OUTPUT}/${basetarget}${extension}" ]]; do post=$(( post + 1 )) basetarget="${origbase}-${post}" done target="${basetarget}${extension}" # -L dereference symlinks, -P no-deref (keep as symlink) # -H follow symlinks if [[ "$softlink$hardlink$movelink" =~ 11 ]]; then _err "Can not do both soft and hard link, or mix with move link" exit 1 fi if [[ "$movelink" -eq 1 ]]; then echo Moving "${1} -> $target" mv "$1" "$target" return $? fi if [[ "$softlink" = 1 ]]; then echo Symbolic link "${1} -> ${OUTPUT}/$target" ln -s "${1}" "${OUTPUT}/$target" return $? fi if [[ "$hardlink" = 1 ]]; then echo Hard link "${1} -> ${OUTPUT}/$target" cp -aTl "${1}" "${OUTPUT}/$target" return $? fi if [[ "$usersync" = 1 ]]; then echo rsync "${1} -> ${OUTPUT}/$target" rsync -ElhprtvxP --info=progress2 --no-i-r "${1}" "${OUTPUT}/$target" return $? fi echo Copying "${1} -> ${OUTPUT}/$target" if [[ -f "${1}" ]]; then if [[ $( stat -c "%s" "$1" ) -gt 100000000 ]]; then if which pv &>/dev/null; then # source is file, and over 100Mb pv "${1}" > "${OUTPUT}/$target" || { keeplink=1 return 1 } # Do not fail even if owner change not working. chmod --reference="${1}" "${OUTPUT}/$target" || true chown --reference="${1}" "${OUTPUT}/$target" || true return fi fi fi cp -aT "${1}" "${OUTPUT}/$target" || keeplink=1 } _remove() { # Remove files based on clipboard if [[ $# -lt 2 ]]; then _help fi for (( i=2; i<=$#; i++ )); do CLIPBOARD=$( echo "${!i}" | tr -d './' | tr -cd '[[:print:]]' ) if [[ ! -e "$STORAGE/${CLIPBOARD}" ]];then _err "No such clipboard: '${CLIPBOARD}'" continue fi _list l "$CLIPBOARD" echo "Sure to delete? [Enter]" read foo while read f; do if [[ ! -e "$f" ]]; then _err "No such path: '$f'" continue fi echo "Removing SOURCE data: ${f}" rm -r "$f" || exit 1 done < "${STORAGE}/$CLIPBOARD" echo "Removing clipboard: ${CLIPBOARD}" rm "$STORAGE/${CLIPBOARD}" done } _delete() { # name if [[ $# -lt 2 ]]; then _help fi for (( i=2; i<=$#; i++ )); do name=$( basename "${!i}" ) if [[ ! -e "$STORAGE/$name" ]]; then _err "No such clipboard '$name'" EC=1 continue fi printf "Clearing %s:\n" "$name" cat "$STORAGE/$name" rm "$STORAGE/$name" done } _delete_all() { [[ -e "$STORAGE" ]] || { exit 0 } _simple_list [[ "$FORCE" -ne 1 ]] && { read -p "Really clear the whole clipboard? ($STORAGE) Break to cancel. " foo } rm -vrf "$STORAGE/" mkdir "$STORAGE" } _err() { echo "${HERR}$@${NO}" } _gui() { # GUI is intended for mc (midnight commander) # Example menu config # 5 cclip # cclip mc %s CLIPBOARD=mc fileargs=$(( $# - 1 )) softlink=0 hardlink=0 keeplink=1 movelink=0 while :; do _list l $CLIPBOARD echo "$fileargs files selected" read -s -p "(q)uit/(c)opy/(p)aste/(d)elete/(e)dit/(b)oard/(o)pts" -n 1 key echo $'\n' if [[ "$key" = "q" ]]; then break fi if [[ "$key" = "c" ]]; then _copy c "${@:2}" break fi if [[ "$key" = "p" ]]; then _paste_clipboard "$CLIPBOARD" break fi if [[ "$key" = "d" ]]; then _delete d mc fi if [[ "$key" = "e" ]]; then _edit e mc fi if [[ "$key" = "b" ]]; then echo -n "Populated clipboards: " _simple_list | xargs echo echo Change board: read -e -i "$CLIPBOARD" CLIPBOARD fi if [[ "$key" = "o" ]]; then _gui_opts fi done } _gui_opts() { while :; do printf "============= Options: m move files: %s s soft link: %s l hard link: %s d keep clipboard: %s e exit Toggle option:\n" "$movelink" "$softlink" "$hardlink" "$keeplink" read -s -n 1 optkey case "$optkey" in m) movelink=$(( 1 - movelink )) ;; s) softlink=$(( 1 - softlink )) ;; l) hardlink=$(( 1 - hardlink )) ;; d) keeplink=$(( 1 - keeplink )) ;; e) break ;; esac done } _get_completer() { self=$( basename $( readlink -f "$0" ) ) echo '_CCLIP_EXEC_complete() { local curr_arg curr_arg=${COMP_WORDS[COMP_CWORD]} if [[ $COMP_CWORD -eq 1 ]]; then COMPREPLY=( $(compgen -W "help autocomplete l list c copy p paste delete Delete" -- "$curr_arg" ) ); fi if [[ $COMP_CWORD -eq 2 ]]; then case ${COMP_WORDS[1]} in d*|e|edit|rm|remove) local IFS=$'"'"'\n'"'"' local remotelist=( $( eval CCLIP_EXEC simplelist ) ) COMPREPLY=( $(compgen -W "${remotelist[*]}" -- "$curr_arg" ) ); ;; p|paste) local IFS=$'"'"'\n'"'"' local remotelist=( $( eval CCLIP_EXEC simplelist ) ) COMPREPLY=( $(compgen -W "${remotelist[*]}" -- "$curr_arg" ) ); ;; c*) COMPREPLY=(); ;; esac fi } complete -o bashdefault -o default -o nospace -F _CCLIP_EXEC_complete CCLIP_EXEC # Run me as: source <( CCLIP_EXEC autocomplete ) ' | sed "s,CCLIP_EXEC,$self,g" exit 0 } _qCol() { # print "easier" mapping of ANSI colors and controls local K="\033[1;30m" local R="\033[1;31m" local G="\033[1;32m" local B="\033[1;34m" local Y="\033[1;33m" local M="\033[1;35m" local C="\033[1;36m" local W="\033[1;37m" local k="\033[2;30m" local r="\033[2;31m" local g="\033[2;32m" local b="\033[2;34m" local y="\033[2;33m" local m="\033[2;35m" local c="\033[2;36m" local w="\033[2;37m" local bk="\033[40m" local br="\033[41m" local bg="\033[42m" local by="\033[43m" local bb="\033[44m" local bm="\033[45m" local bc="\033[46m" local bw="\033[47m" local S='\033[1m' #strong local s='\033[2m' #strong off local U='\033[4m' #underline local u='\033[24m' #underline off local z='\033[0m' #zero colors local Z='\033[0m' #zero colors local ic='\033[7m' #inverse colors local io='\033[27m' #inverse off local st='\033[9m' #strike on local so='\033[29m' #strike off local CLR='\033[2J' # Clear screen local CLREND='\033[K' # Clear to end of line local CLRBEG='\033[1K' # Clear to beginning of line local CLRLNE='\033[2K' # Clear the line local CLRSCR="$CLR"'\033[0;0H' # Clear screen, reset cursor local color_keys=" K R G B Y M C W k r g b y m c w S s U u z Z \ ic io st so bk br bg by bb bm bc bw \ CLR CLREND CLRBEG CLRLNE CLRSCR " local arg val for ((arg=1;arg<=$#;arg++)) { val=${!arg} [[ ${color_keys} = *" $val "* ]] || { echo "No such color code '${val}'" >&2; return 1; } printf ${!val} } } for (( i=1; i<=$#; i++ )); do [[ "${!i}" = "-h" ]] && _help [[ "${!i}" = "--help" ]] && _help done _load_config CMD="${1:-list}" CLIPBOARD=0 OUTPUT=. [[ "$CMD" = help || "$CMD" = h || "$CMD" = "-h" || "$CMD" = "--help" ]] && { _help; exit; } [[ "$CMD" = "l" || "$CMD" = "list" ]] && { _list "$@"; exit; } [[ "$CMD" = "c" || "$CMD" = "copy" ]] && { _copy "$@"; exit; } [[ "$CMD" = "d" || "$CMD" = "delete" || "$CMD" = "del" ]] && { _delete "$@"; exit; } [[ "$CMD" = "D" || "$CMD" = "Delete" || "$CMD" = "Del" ]] && { _delete_all; exit; } [[ "$CMD" = "e" || "$CMD" = "edit" ]] && { _edit "$@"; exit; } [[ "$CMD" = "p" || "$CMD" = "paste" || "$CMD" =~ ^p.$|^p..$ ]] && { _paste "$@"; exit; } [[ "$CMD" = "rm" || "$CMD" = "remove" ]] && { _remove "$@"; exit; } [[ "$CMD" = "mc" ]] && { _gui "$@"; exit; } [[ "$1" = "autocomplete" ]] && { _get_completer; exit; } [[ "$1" = "simplelist" ]] && { _simple_list; exit; } echo "Not a command: '$CMD'" _shorthelp exit $EC