├── 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 | 
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 |
--------------------------------------------------------------------------------