├── cb ├── license.text ├── meld.png └── readme.md /cb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | is_exec() { case "$0" in */cb) : ;; esac; } 4 | 5 | if is_exec; then set -eu; fi 6 | 7 | case "${OSTYPE:-}$(uname)" in 8 | [lL]inux*) ;; 9 | [dD]arwin*) mac_os=1 ;; 10 | [cC]ygwin) win_os=1 ;; 11 | *) echo "Unknown operating system \"${OSTYPE:-}$(uname)\"." >&2; false ;; 12 | esac 13 | 14 | is_wayland() { [ "$XDG_SESSION_TYPE" = 'wayland' ]; } 15 | is_mac() { [ ${mac_os-0} -ne 0 ]; } 16 | is_win() { [ ${win_os-0} -ne 0 ]; } 17 | 18 | if is_mac; then 19 | alias cbcopy=pbcopy 20 | alias cbpaste=pbpaste 21 | elif is_win; then 22 | alias cbcopy=putclip 23 | alias cbpaste=getclip 24 | else 25 | if is_wayland; then 26 | alias cbcopy=wl-copy 27 | alias cbpaste=wl-paste 28 | else 29 | alias cbcopy='xclip -selection clipboard' 30 | alias cbpaste='xclip -selection clipboard -out' 31 | fi 32 | fi 33 | 34 | cb() { 35 | if [ -t 0 ]; then 36 | # stdin is connected to a terminal. 37 | cbpaste "$@" 38 | else 39 | cbcopy "$@" 40 | fi 41 | } 42 | 43 | if is_exec; then cb "$@"; fi 44 | 45 | -------------------------------------------------------------------------------- /license.text: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /meld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niedzielski/cb/4eac52f7b39e383a9c4dd09f7bd3fadb9f61a1df/meld.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 📋 cb 2 | 3 | Universal command-line clipboard with automatic copy and paste detection. Eg, 4 | `cb|sort|cb`. 5 | 6 | ## Purpose 7 | 8 | **cb** is a clipboard for working between graphical and command-line interfaces 9 | (GUIs and CLIs). The clipboard is a surprisingly convenient integration between 10 | the two and doesn't use temporary files. cb automatically performs a copy or a 11 | paste based on the context. 12 | 13 | ### Why you don't need cb 14 | 15 | If any of the following are true, you do **not** need cb: 16 | 17 | - You use GUIs exclusively. 18 | - You use CLIs exclusively. 19 | - You can use the system equivalent copy-and-paste CLIs directly. Eg, 20 | [XSel](https://github.com/kfish/xsel) or `pbcopy` / `pbpaste`. 21 | - You prefer using temporary files when working across GUIs and CLIs. 22 | 23 | All cb provides is a consistent interface across operating systems. Any 24 | clipboard CLI will do. What's important is how you use the tool. 25 | 26 | ## Usage 27 | 28 | Supply standard input to **copy**: 29 | 30 | ```console 31 | $ echo abc|cb 32 | ``` 33 | 34 | Supply no input to **paste**: 35 | 36 | ```console 37 | $ cb 38 | abc 39 | ``` 40 | 41 | Both copy and paste can appear in the same command: 42 | 43 | ```console 44 | $ curl "$(cb)"|cb 45 | ``` 46 | 47 | Contents on the clipboard can be used bidirectionally with other GUIs like 48 | graphical text editors or web browsers using the usual shortcut (control-c/v or 49 | command-c/v). 50 | 51 | ## Installation 52 | 53 | ```bash 54 | # Download the script to ~/bin/cb. 55 | curl https://raw.githubusercontent.com/niedzielski/clipboard/main/cb -o ~/bin/cb && 56 | 57 | # Make the script executable. 58 | chmod +x ~/bin/cb 59 | ``` 60 | 61 | cb also supports sourcing in Almquist-like shells like `. cb` or `source ./cb`. 62 | 63 | The heart of cb is tiny so edit it however you like and discard the rest! 64 | 65 | ### Dependencies 66 | 67 | cb requires the following dependencies to be installed: 68 | 69 | - Linux: xclip on X (eg, `sudo apt install xclip`) and wl-clipboard on Wayland 70 | (`sudo apt install wl-clipboard`). 71 | - macOS: none (`pbcopy` / `pbpaste` are installed by default). 72 | - Windows: CygUtils (install `putclip` / `getclip` via Cygwin GUI). 73 | 74 | If in doubt, it's simplest to just try executing cb. 75 | 76 | ### Troubleshooting 77 | 78 | Is `~/bin` in the `PATH` environment variable? 79 | `grep --only-matching ~/bin <<< "$PATH"` should report a match. If not, add it 80 | like `PATH="$PATH":~/bin`. 81 | 82 | ## Examples 83 | 84 | All of these examples appear trivial because copy-and-paste is ubiquitous. The 85 | intent is to demonstrate the understated usefulness of the system clipboard for 86 | CLI-GUI integration. You can work seamlessly across GUIs and CLIs with it. 87 | 88 | - Sort clipboard lines: `cb|sort|cb`.
Expand for 89 | detail… 90 | 91 | ```console 92 | $ # Simulate copying some lines of text from another program with control or command-C. 93 | 94 | $ printf 'c\nb\na'|cb 95 | 96 | $ cb 97 | c 98 | b 99 | a 100 | 101 | $ # Sort the clipboard's contents by line. 102 | 103 | $ cb|sort|cb 104 | 105 | $ # Simulate pasting the text back to another program with control or command-V. 106 | 107 | $ cb 108 | a 109 | b 110 | c 111 | ``` 112 | 113 |
114 | 115 | - Count the number of bytes, characters, and lines on the clipboard: 116 | `cb|wc --bytes --chars --lines`.
Expand for 117 | detail… 118 | 119 | ```console 120 | $ # Simulate copying text from another program with control or command-C. 121 | 122 | $ echo abc|cb 123 | 124 | $ cb|wc --bytes --chars --lines 125 | 1 4 4 126 | ``` 127 | 128 |
129 | 130 | - Replace single quotes with double quotes on the clipboard: 131 | `cb|sed s%\'%\"%g|cb`.
Expand for detail… 132 | 133 | ```console 134 | $ # Simulate copying text from another program with control or command-C. 135 | 136 | $ cb <<<\'abc\' 137 | 138 | $ cb|sed s%\'%\"%g|cb 139 | 140 | $ # Simulate pasting the text back to another program with control or command-V. 141 | 142 | $ cb 143 | "abc" 144 | ``` 145 | 146 |
147 | 148 | - Diff the clipboard with a file: `diff <(cb) right-hand-side.text`. Works with 149 | diff GUIs too: `meld <(cb) right-hand-side.text`. 150 |
Expand for detail… 151 | 152 | ```console 153 | $ # Simulate copying some lines of text from another program with control or command-C. 154 | 155 | $ cb << 'eof' 156 | a 157 | b 158 | eof 159 | 160 | $ # Simulate a previously saved reference. 161 | 162 | $ cat << 'eof' > right-hand-side.text 163 | a 164 | b 165 | c 166 | eof 167 | 168 | $ # Diff the contents of the clipboard with the reference. 169 | 170 | $ diff <(cb) right-hand-side.text 171 | 2a3 172 | > c 173 | 174 | $ # View the same diff in Meld, a graphical diffing program. 175 | 176 | $ meld <(cb) right-hand-side.text 177 | ``` 178 | 179 | ![Example visual difference of the clipboard (left-hand side) and right-hand-side.text in Meld.](meld.png) 180 |
181 | 182 | - Download a file from the URL on the clipboard: `wget "$(cb)"`. 183 | 184 | - Download a video from the YouTube URL on the clipboard: `youtube-dl "$(cb)"`. 185 |
Expand for detail… 186 | 187 | ```console 188 | $ # Simulate copying a URL from a browser address bar with control or command-C. 189 | 190 | $ echo 'https://www.youtube.com/watch?v=92c8vW-AzAc'|cb 191 | 192 | $ # Download the address from the clipboard URL. 193 | 194 | $ youtube-dl "$(cb)" 195 | [youtube] 92c8vW-AzAc: Downloading webpage 196 | WARNING: Requested formats are incompatible for merge and will be merged into mkv. 197 | [download] Destination: Fritz roars-92c8vW-AzAc.f137.mp4 198 | [download] 100% of 5.07MiB in 01:37 199 | [download] Destination: Fritz roars-92c8vW-AzAc.f251.webm 200 | [download] 100% of 175.94KiB in 00:02 201 | [ffmpeg] Merging formats into "Fritz roars-92c8vW-AzAc.mkv" 202 | Deleting original file Fritz roars-92c8vW-AzAc.f137.mp4 (pass -k to keep) 203 | Deleting original file Fritz roars-92c8vW-AzAc.f251.webm (pass -k to keep) 204 | 205 | $ ls Fritz\ roars-92c8vW-AzAc.mkv 206 | 'Fritz roars-92c8vW-AzAc.mkv' 207 | ``` 208 | 209 |
210 | 211 | - Copy the version of Chromium to the clipboard: `chromium --version|cb`. 212 |
Expand for detail… 213 | 214 | ```console 215 | $ # Copy the version of Chromium installed into the clipboard. 216 | 217 | $ chromium --version|cb 218 | 219 | $ # Simulate pasting the version into another program with control or command-V. 220 | 221 | $ cb 222 | Chromium 97.0.4692.99 built on Debian bookworm/sid, running on Debian bookworm/sid 223 | ``` 224 | 225 |
226 | 227 | - Copy 10k numbered lines to the clipboard: 228 | `for ((i=0; i < 10000; i++)); do echo $i; done|cb`. 229 |
Expand for detail… 230 | 231 | ```console 232 | $ # Copy numbered lines from 0 to 10000 to the clipboard. 233 | 234 | $ for ((i=0; i < 10000; i++)); do echo $i; done|cb 235 | 236 | $ # Simulate pasting the text into another program with control or command-V. 237 | 238 | $ cb|head 239 | 0 240 | 1 241 | 2 242 | 3 243 | 4 244 | 5 245 | 6 246 | 7 247 | 8 248 | 9 249 | ``` 250 | 251 |
252 | 253 | - Replace newlines on the clipboard with commas: 254 | `cb|node --input-type=module --eval 'import fs from "fs/promises"; const text = await fs.readFile("/dev/stdin", "utf-8"); console.log(text.split("\n").join())'|cb`. 255 |
Expand for detail… 256 | 257 | ```console 258 | $ # Simulate copying text delimited by newlines from another program with control or command-C. 259 | 260 | $ echo -ne 'a\nb\nc'|cb 261 | 262 | $ cb|node --input-type=module --eval 'import fs from "fs/promises"; const text = await fs.readFile("/dev/stdin", "utf-8"); console.log(text.split("\n").join())'|cb 263 | 264 | $ # Simulate pasting the CSV back to another program with control or command-V. 265 | 266 | $ cb 267 | a,b,c 268 | ``` 269 | 270 |
271 | 272 | - Pretty print JSON: 273 | `cb|node -pe 'JSON.stringify(JSON.parse(require("fs").readFileSync(0, "utf-8")), null, 2)'|cb`. 274 |
Expand for detail… 275 | 276 | ```console 277 | $ # Simulate copying a blob of JSON from another program with control or command-C. 278 | 279 | $ cb <<<'{"a":1,"b":2,"c":3}' 280 | 281 | $ cb|node -pe 'JSON.stringify(JSON.parse(require("fs").readFileSync(0, "utf-8")), null, 2)'|cb 282 | 283 | $ # Simulate pasting the JSON back to another program with control or command-V. 284 | 285 | $ cb 286 | { 287 | "a": 1, 288 | "b": 2, 289 | "c": 3 290 | } 291 | ``` 292 | 293 |
294 | 295 | - Test if the random patch you found online and copied to your clipboard applies 296 | to your code: `git apply --check <(cb)`. 297 | 298 | - Dump the HEAD revision of a file to the clipboard: 299 | `git show HEAD:readme.md|cb`. 300 | 301 | - Reverse clipboard line order: `cb|tac|cb`. 302 | 303 | - Copy an image to the clipboard: `cb < banana.png`. 304 | 305 | - Wrap clipboard text at 72 characters: 306 | `cb|fold --spaces --width=72|sed 's% \+$%%'|cb`. 307 | 308 | - Find songs, shuffle them, and copy them to the clipboard: 309 | `find -iname \*.flac -printf %f\\n|shuf|cb`. 310 | 311 | - Copy the absolute path of a filename: 312 | `realpath --canonicalize-missing --no-symlinks "$(cb)"|cb`. 313 | 314 | - Voice the clipboard: `cb|espeak`. 315 | 316 | - Truncate the clipboard: `cb|tail|cb`. 317 | 318 | - Save the clipboard to a transient file: 319 | `t="$(mktemp)" && cb >| "$t" && echo "$t"`. 320 | 321 | - Edit the clipboard contents in a temporary buffer `cb|vim -`. 322 | 323 | - Compare Gzip and Brotli compressions of the clipboard: 324 | `cb|gzip --best|wc --bytes && cb|brotli --best|wc --bytes`. 325 | 326 | - Copy the most recent photo taken on an Android device to the clipboard: 327 | `adb exec-out 'cat "$(ls -c1 /sdcard/DCIM/Camera/IMG*.jpg|tail -n1)"'|cb`. 328 | 329 | - Clear the clipboard: `cb < /dev/null`. 330 | 331 | ## License (Public Domain) 332 | 333 | All code in this repository is public domain and may be used without limitation. 334 | --------------------------------------------------------------------------------