Skip to content

Instantly share code, notes, and snippets.

@RichardBronosky
Last active March 15, 2024 09:47
Show Gist options
  • Save RichardBronosky/56d8f614fab2bacdd8b048fb58d0c0c7 to your computer and use it in GitHub Desktop.
Save RichardBronosky/56d8f614fab2bacdd8b048fb58d0c0c7 to your computer and use it in GitHub Desktop.
cb - A leak-proof tee to the clipboard - Unify the copy and paste commands into one intelligent chainable command.

cb

A leak-proof tee to the clipboard

This script is modeled after tee (see man tee) and works on Linux, macOS, Cygwin, WSL/WSL2

It's like your normal copy and paste commands, but unified and able to sense when you want it to be chainable.

This project started as an answer to the StackOverflow question: How can I copy the output of a command directly into my clipboard?

Examples

Copy

$ echo -n $(date) | cb
# clipboard contains: Tue Jan 24 23:00:00 EST 2017
# but, because of `echo -n` there is no newline at the end

Paste

# clipboard retained from the previous block
$ cb
Tue Jan 24 23:00:00 EST 2017
# above there is a newline after the output for readability
# below there is no newline because cb recognized that it was outputing to a pipe and any alterations would "contaminate" the data
$ cb | cat
Tue Jan 24 23:00:00 EST 2017$ cb > foo
# look carefully at this   ^^ that's a new $ prompt at the end of the content exactly as copied
$ cat foo
Tue Jan 24 23:00:00 EST 2017

Chaining

$ date | cb | tee updates.log
Tue Jan 24 23:11:11 EST 2017
$ cat updates.log
Tue Jan 24 23:11:11 EST 2017
# clipboard contains: Tue Jan 24 23:11:11 EST 2017
#!/bin/bash
# from: https://gist.github.com/RichardBronosky/56d8f614fab2bacdd8b048fb58d0c0c7
LINUX_copy(){
cat | xclip -selection clipboard
}
LINUX_paste(){
xclip -selection clipboard -o
}
WSL_copy(){
cat | /mnt/c/Windows/System32/clip.exe
}
WSL_paste(){
/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe Get-Clipboard | sed 's/\r//'
}
CYGWIN_copy(){
cat > /dev/clipboard
}
CYGWIN_paste(){
cat /dev/clipboard
}
MAC_copy(){
cat | pbcopy
}
MAC_paste(){
pbpaste
}
stdin_is_a_pipe(){
[[ -p /dev/stdin ]]
}
stdin_is_a_tty(){
[[ -t 0 ]]
}
stdin_is_pipe_like(){
stdin_is_a_pipe || ! stdin_is_a_tty
}
stdout_is_pipe_like(){
! stdout_is_a_tty # meaning # it must be a pipe or redirection
}
stdout_is_a_tty(){
[[ -t 1 ]]
}
requested_open_ended(){
[[ "${args[0]:-}" == "-" ]]
}
requested_test_suite(){
[[ "${args[0]:-}" == "--test" ]]
}
enable_tee_like_chaining(){
# see `man tee`
if stdout_is_pipe_like; then
${os}_paste
elif requested_open_ended; then
${os}_paste
echo
fi
}
prevent_prompt_from_being_on_the_same_line(){
if stdout_is_a_tty; then # we don't have to be strict about not altering the output
echo
fi
}
detect_os(){
if [[ -f /proc/version ]] && grep -iq Microsoft /proc/version; then
printf WSL
else
case "$(uname -s)" in
Linux*) printf LINUX;;
Darwin*) printf MAC;;
CYGWIN*) printf CYGWIN;;
esac
fi
}
test_suite(){
# setup
printf '1234' | ${BASH_SOURCE[0]}
printf "\n1. output to TTY\n"
${BASH_SOURCE[0]}
printf "1234 should be above.\n"
printf "\n2. output to pipe\n"
${BASH_SOURCE[0]} | cat; echo
printf "1234 should be above.\n"
printf "\n3. input from pipe and output to pipe\n"
printf '1234' | ${BASH_SOURCE[0]} | cat; echo
printf "1234 should be above.\n"
}
function debug(){
stdin_is_a_pipe && echo "stdin_is_a_pipe: 1" >> /tmp/ono || echo "stdin_is_a_pipe: 0" >> /tmp/ono
stdin_is_a_tty && echo "stdin_is_a_tty: 1" >> /tmp/ono || echo "stdin_is_a_tty: 0" >> /tmp/ono
stdin_is_pipe_like && echo "stdin_is_pipe_like: 1" >> /tmp/ono || echo "stdin_is_pipe_like: 0" >> /tmp/ono
stdout_is_pipe_like && echo "stdout_is_pipe_like: 1" >> /tmp/ono || echo "stdout_is_pipe_like: 0" >> /tmp/ono
stdout_is_a_tty && echo "stdout_is_a_tty: 1" >> /tmp/ono || echo "stdout_is_a_tty: 0" >> /tmp/ono
echo >> /tmp/ono
}
main(){
os="$(detect_os)"
if stdin_is_pipe_like; then
${os}_copy
enable_tee_like_chaining
else # stdin is not pipe-like
${os}_paste
prevent_prompt_from_being_on_the_same_line
fi
}
args=("$@")
if requested_test_suite; then
export DEBUG=1
test_suite
else
[[ ${DEBUG:-} == 1 ]] && debug
main
fi
@javier-lopez
Copy link

Great idea =)!, I made some editions, mostly to avoid having separate scripts for different platforms:

https://github.com/javier-lopez/learn/blob/master/sh/tools/cb

@RichardBronosky
Copy link
Author

I ran with your "avoid having separate scripts for different platforms" idea. I also added support for WSL (Window Services for Linux).

@L1so
Copy link

L1so commented Jan 22, 2022

For some reason, using this in ssh session (with X11 forwarding), hangs ssh on logout. I think there's still open process.

@padames
Copy link

padames commented May 1, 2022

@L1so I found the same if you refer to hanging after executing the example command-line
date | cb | tee updates.log
It hangs in Ubuntu 18.04 LTS terminal.
Running cb --test also hangs on the last test. which means this is broken in Linux.

@RichardBronosky
Copy link
Author

@L1so @padames I never considered that someone might try running 'cb' inside of ssh. What would you expect to happen on localhost and remote host when you do date | cb or cb > out.txt? Which host's clipboard is modified/read?

@coppolat1
Copy link

On WSL, I was getting an unwanted trailing whitespace, so I modified the WSL copy as follows

WSL_copy(){
    cat | perl -pe 'chomp if eof' | /mnt/c/Windows/System32/clip.exe
}

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