#!/bin/bash set -e _help() { SELF=$( basename "$0" ) echo "Console Clipboard manager Usage: $SELF [command] [clipboard name] [filename] Short Command Description c copy Copy from file / stdin x cut Move file to clipboard p paste Paste to file / stdout o move Paste to file, by moving (clipboard deleted) d delete Delete an entry D Delete Clear the whole clibboard e edit Edit contents, with \$EDITOR l list [default] List names of clipboards pmenu pastemenu Paste a file/folder using a simple menu (ex. for MC) autocomplete Get Bash autocompletion script Clipboard Name: Any string for the clipboard name. default: 0 Filename: If not given, defaults to clipboard name - uses stdout/stdin When reading from clipboard: File or folder to write the clipboard contents. If omitted: stdout When writing to clipboard: File or folder to read from. If omitted: stdin Shorthand: echo my data | $SELF 1 # writes data with name: 1 $SELF 1 | cat - # prints the contents of file: 1 Config: CCLIP_HOME environment variable sets clipboard storage path, defauls to ~/.cache/cclip " exit } _load_config() { STORAGE=${CCLIP_HOME:-~/.cache/cclip} [[ -d "$STORAGE" ]] || { _msg "Creating $STORAGE folder, and inserting sample data" mkdir -p "$STORAGE" echo Sample data | _write_stdin 0 } } _list() { cd "$STORAGE" [[ $(ls -A) ]] || { _msg "No clipboard entries" return } hl=$( _qCol W 2>/dev/null || true ) he=$( _qCol y U 2>/dev/null || true ) no=$( _qCol z 2>/dev/null || true ) now=$( date -Idate ) printf "%s%1s %5s %10s %s%s\n" "$he" T Size Added Name "$no" IFS=$'\n' while read n; do size=$( du -k -h -s --apparent-size "$n" | awk '{ print $1 }' ) type=$( stat --printf "%A" "$n" ) type=${type:0:1} if [[ "$type" = "-" ]]; then type=f; fi date=$( stat --printf "%y" "$n" ) if [[ "$date" = "$now"* ]]; then date=${date#* } date="${date:0:8}" else date=${date%% *} #~ date="${date:0:8}" fi printf "%1s %5s %10s %s%s%s\n" \ "${type}" \ "$size" \ "${date}" \ "$hl" "$n" "$no" done < <( ls -t ) } _simple_list() { ls -1 "$STORAGE" } _write() { # no file mentioned, use stdin [[ -z "$FILE" ]] && stream_in=1 # no file mentioned, use the name as file [[ -z "$FILE" ]] && { [[ -e "$NAME" ]] && { # NAME is actually the file, reverse roles FILE="$NAME" NAME=$( basename "$NAME" ) } } [[ -e "$FILE" ]] && { # Input file exists, dont use stream stream_in=0 } [[ $stream_in -ne 1 ]] && [[ -z "$FILE" ]] && { _msg File to read needed, or use stdin exit 1 } # sure to write something, delete target first rm -rf "$STORAGE/$NAME" [[ "$stream_in" -eq 1 ]] && { _write_stdin "$NAME" } || { _write_files "$NAME" "$FILE" } } _write_files() { # name, file if [[ "$1" = "$2" ]]; then to="" else to=" to $1" fi if [[ $MOVE = true ]]; then echo Cutting "${2}$to" mv "$2" "$STORAGE/$1" else echo Copying "${2}$to" cp -aT "$2" "$STORAGE/$1" fi touch -h "$STORAGE/$1" } _write_stdin() { # name cat - > "$STORAGE/$1" } _read_menu() { pushd "$STORAGE" &>/dev/null echo "Select clipboard from menu" ls read -e NAME popd &>/dev/null NAME=${NAME#\'} NAME=${NAME#\"} NAME=${NAME%\'} NAME=${NAME%\"} if [[ -e "$STORAGE/$NAME" ]]; then NAME=$( basename "$NAME" ) _read else echo "Could not find '$NAME'" fi } _read() { [[ -e "$STORAGE/$NAME" || -L "$STORAGE/$NAME" ]] || { _msg "No such clipboard: '$NAME'" return } [[ "$FILE" = "-" ]] && stream_out=1 [ -t 1 ] || stream_out=1 [[ "$stream_out" -eq 1 ]] && { _read_stdout "$NAME" return } # no file mentioned, use the name as file [[ -z "$FILE" ]] && { [[ -n "$NAME" ]] && { FILE=$( basename "$NAME" ) } [[ -e "$FILE" ]] && { echo "'$FILE' already exists, not overwriting" exit 1 } } _read_files "$NAME" "$FILE" } _read_files() { # name, file if [[ "$1" = "$2" ]]; then to="" else to=" to $2" fi if [[ $MOVE = true ]]; then echo Moving "${1}$to" mv "$STORAGE/$1" "$2" else echo Pasting "${1}$to" cp -aT "$STORAGE/$1" "$2" fi } _read_stdout() { # name cat "$STORAGE/$1" } _delete() { # name [[ -e "$STORAGE/$1" ]] || { _msg No such clipboard exit 1 } [[ "$FORCE" -ne 1 ]] && { read -p "Really delete $1 ? Break to cancel. " foo } echo Deleting "$1" rm -rf "$STORAGE/$1" } _delete_all() { [[ -e "$STORAGE" ]] || { exit 0 } [[ "$FORCE" -ne 1 ]] && { read -p "Really delete $STORAGE ? Break to cancel. " foo } rm -vrf "$STORAGE/" mkdir "$STORAGE" } _msg() { echo "$@" >&2 } _get_name() { [[ "$ARG1" = "$CMD" ]] && { NAME="$ARG2" } || { NAME="$ARG1" } [[ -z "$NAME" ]] && NAME=0 return 0 } _get_file() { [[ "$ARG1" = "$NAME" ]] && { FILE="$ARG2" } [[ "$ARG2" = "$NAME" ]] && { FILE="$ARG3" } return 0 } _edit() { EDITOR=${EDITOR:-vi} [[ -d "$STORAGE/$NAME" ]] && { _msg "$NAME is a directory, can not edit" exit 1 } $EDITOR "$STORAGE/$NAME" } _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 x cut p paste o move delete Delete pmenu" -- $curr_arg ) ); fi if [[ $COMP_CWORD -eq 2 ]]; then case ${COMP_WORDS[$(( $COMP_CWORD - 1 ))]} in o|move|p|paste|d*) local IFS=$'"'"'\n'"'"' local remotelist=( $( eval CCLIP_EXEC simplelist ) ) COMPREPLY=( $(compgen -W "${remotelist[*]}" -- $curr_arg ) ); ;; x|c*) COMPREPLY=( $(compgen -f -d -- $curr_arg ) ); ;; esac fi if [[ $COMP_CWORD -eq 3 ]]; then COMPREPLY=( $(compgen -f -d -- $curr_arg ) ); fi } complete -F _CCLIP_EXEC_complete CCLIP_EXEC # Run me as: source <( CCLIP_EXEC autocomplete ) ' | sed "s,CCLIP_EXEC,$self,g" exit 0 } for (( i=1; i<=$#; i++ )); do [[ "${!i}" = "-h" ]] && _help [[ "${!i}" = "--help" ]] && _help done _load_config source qolop &>/dev/null || true ARG1="$1" ARG2="$2" ARG3="$3" MOVE=false CMD=help if [[ -z "$1" ]]; then CMD=list; fi [[ "$1" = "p" || "$1" = "paste" ]] && { CMD=read; ARG1=$CMD; } [[ "$1" = "o" || "$1" = "move" ]] && { CMD=read; ARG1=$CMD; MOVE=true; } [[ "$1" = "pmenu" || "$1" = "pastemenu" ]] && { CMD=read_menu; ARG1=$CMD; } [[ "$1" = "c" || "$1" = "copy" ]] && { CMD=write; ARG1=$CMD; } [[ "$1" = "x" || "$1" = "cut" ]] && { CMD=write; ARG1=$CMD; MOVE=true; } [[ "$1" = "d" || "$1" = "delete" || "$1" = "del" ]] && { CMD=delete; ARG1=$CMD; } [[ "$1" = "D" || "$1" = "Delete" || "$1" = "Del" ]] && { CMD=delete_all; ARG1=$CMD; } [[ "$1" = "l" || "$1" = "list" ]] && { CMD=list; ARG1=$CMD; } [[ "$1" = "e" || "$1" = "edit" ]] && { CMD=edit; ARG1=$CMD; } [[ "$1" = "h" || "$1" = "help" ]] && _help [[ "$1" = "autocomplete" ]] && { _get_completer; exit; } [[ "$1" = "simplelist" ]] && { _simple_list; exit; } [[ -n "$1" ]] && [[ -e "$STORAGE"/"$1" ]] && CMD=read # if stdout redirected, default to read [ -t 1 ] || CMD=read # if stdin comes from stream, default to write [ -t 0 ] || CMD=write [[ "$CMD" = help ]] && { _help exit $? } [[ "$CMD" = list ]] && { _list exit $? } [[ "$CMD" = delete_all ]] && { _delete_all exit $? } _get_name _get_file [[ "$CMD" = read ]] && { _read exit $? } [[ "$CMD" = read_menu ]] && { _read_menu exit $? } [[ "$CMD" = delete ]] && { _delete "$NAME" exit $? } [[ "$CMD" = write ]] && { _write exit $? } [[ "$CMD" = edit ]] && { _edit exit $? } # TODO: # folders written as TAR # content encrypted with conf variable?