Skip to content

Instantly share code, notes, and snippets.

@ernstki
Last active July 22, 2019 19:15
Show Gist options
  • Save ernstki/a2e8237a0fdede6a8e553495b1d08301 to your computer and use it in GitHub Desktop.
Save ernstki/a2e8237a0fdede6a8e553495b1d08301 to your computer and use it in GitHub Desktop.
doclip - manipulate clipboard text easily from the command line (macOS and Linux)
#!/usr/bin/env bash
#
# Manipulate clipboard contents easily; optionally ensures a final newline is
# written to the output
#
# NB: If symlinked as 'reclip[.sh]', automatically re-copies to the clipboard
# (just as if you specified '-r')
#
# Author: Kevin Ernst <ernstki -at- mail.uc.edu>
# URL: https://gist.github.com/ernstki/a2e8237a0fdede6a8e553495b1d08301
#
set -ueo pipefail
# set TRACE=1 in the environment to trace script execution
(( ${TRACE:-} )) && set -x
DEBUG=${DEBUG:-}
ME=$( basename "$BASH_SOURCE" )
USAGE="
usage:
$ME [-h|--help] [-c|--columns] [-m|--max-line-length]
$ME [-r|--recopy] [-n|--newline] [-x|--xargs] [<cmd> | -s|--sed <expr>]
where:
-h, --help you're looking at it ;)
-c, --columns show column/character count of clipboard contents; good for
checking the length of a line (shortcut for 'doclip wc -m')
-m, --max-line-length
compute and display the maximum length for all the input
lines; like 'wc -L' (but that's GNU/Linux-only)
-r, --recopy re-copy results to the clipboard (default: print to stdout)
-n, --newline ensure output ends with a newline (not needed w/ 'sed')
-x, --xargs run <cmd> as many time as there are input lines, each time
with one input line as its argument, properly quoted; if
order doesn't matter, add '-P <nprocs>' to speed things up
<cmd> pipe clipboard contents through this command, which may be
a quoted string, including pipes (e.g., 'par w60')
-s <expr> expression fed directly to 'sed'; also used to prevent
auto-globalization of a detected sed 's///' expression
(see below)
When called as 'reclip' (create a symlink to do this), this script behaves
as if the '-r' (re-copy results) option had been given.
Pass '--' after any 'doclip' options to treat any further arguments as part
of the command, e.g., 'doclip -r -- xargs du -s \\| sort -r -n -k1'
(where '-s' and '-n' would otherwise be recognized as 'doclip' options).
If <expr> begins with 's/', the 'g' flag is appended for you if you forget,
and the '-s' switch may be omitted (try 's/ /_/'); if you don't want your
sed expression messed with, specify '-s' / '--sed'.
"
LITERALSED=
ADDNEWLINE=
RECOPY=
XARGS=
STOPARGS=
CMD=()
sedexp=
# on macOS/BSD, piping through 'sed' will always add a terminating newline
function newliner() { sed; }
# compute maximum length of all input lines
function maxlength() { awk '{if (length>max) {max=length}} END{print max}'; }
# quote input lines to protect spaces / metacharacters
#function shellquote() { printf "%q" "$1"; }
if [[ `uname -s` != Darwin ]]; then
if ! which xclip &>/dev/null; then
echo >&2
echo "ACK! Missing required external utility 'xclip'." >&2
echo " Try installing it with your distro's package manager." >&2
echo >&2
exit 1
fi
function pbcopy() { xclip -i -sel clip; }
function pbpaste() { xclip -o -sel clip; }
# on Linux, use Perl to ensure every line ends with a LF character
function newliner() { perl -ne 'chomp; print "$_\n"'; }
fi
# redundant but still allowed if '-c' is supplied; Bash has no 'unshift'
[[ $ME =~ ^reclip ]] && RECOPY=1
while (( $# )); do
case $1 in
--)
STOPARGS=1
;;
-*)
# don't process any options flags after '--' given
if (( STOPARGS )); then
CMD+=("$1")
shift; continue
fi
# otherwise, prcess 'doclip' options
case $1 in
# help
-h|-\?|--h*)
echo "$USAGE"
exit
;;
# column count
-c|--c*)
CMD=(wc -m)
;;
# max line length
-m|--max*)
CMD=(maxlength)
;;
# copy back to clipboard
-r|--r*)
RECOPY=1
echo "Re-copying results back to clipboard." >&2
;;
# (literal) sed expression
-s|--s*)
shift
LITERALSED=1
CMD=(sed "$1")
;;
# ensure output ends in a newline
-n|--n*)
ADDNEWLINE=1
;;
# send input through 'xargs -L1'
-x|--x*)
XARGS=1
;;
# Unknown options assumed to be for the program...
*)
CMD+=("$1")
;;
# ...if you want to *force* the use of '--' to separate them,
# uncomment this
#*)
# echo >&2
# echo "ACK! Unknown $ME option '$1'." >&2
# echo " Maybe try '$ME -- program <args>' instead?" >&2
# echo >&2
# exit 1
# ;;
esac
;;
s/*)
sedexp=$1
# if user gave '-s' / '--sed', don't mess with it
# otherwise, make it global, unless it already was
if (( !LITERALSED )); then
[[ $1 =~ /g$ ]] || sedexp="$1g"
fi
CMD=(sed "$sedexp")
;;
*)
CMD+=("$1")
;;
esac
shift
done
cmd='pbpaste'
if (( XARGS )); then
# somehow this actually works; arguments w/ spaces are quoted, and
# '${CMD[@]}' gets expanded and individually double-quoted when 'eval'd
cmd+=' | xargs -L 1 -I {} "${CMD[@]}" {}'
else
# if CMD is empty, don't even include it in the pipeline; otherwise ensure
# the arguments are individually quoted once expanded in the 'eval'
[[ ${CMD:-} ]] && cmd+='| "${CMD[@]}"'
fi
# if ADDNEWLINE=1, use the 'newliner' function to guarantee a final newline
# if RECOPY=1, pipe through 'pbcopy' to put result back in clipboard
(( DEBUG )) && set -x
eval "$cmd ${ADDNEWLINE:+| newliner} ${RECOPY:+| pbcopy}"
set +x
@ernstki
Copy link
Author

ernstki commented Jul 22, 2019

Some useful sed scripts that you can put in your PATH for use with doclip:

#!/usr/bin/env sed -f
# variableize - change input text to be a valid programming language identifier
s/[^[:alnum:]_]/_/g
s/__*/_/g
#!/usr/bin/env sed -f
# underscore - replace spaces in input text with underscores
s/ /_/g

Also see unwrap (remove line breaks), recase (upper- or lower-case input text based on what name the script is called as), reslashify (convert forward ↔︎ backslashes in a pathname), and titlecase (title-case the input according to AP or Chicago style guides).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment