0

If I have two terminals in different working directories, and I want to move a file from the PWD of one terminal to PWD of the other without using mv (because mv requires me to type out at least one of the directories)

We can implement a very simple "cut" and "paste" functionality by implementing them as a bash script:

# cut (pseudocode)
fname=<target>
mv <target> /tmp/the-cut-file
# paste
mv /tmp/the-cut-file ./$fname

I wanted to know if standard tools exist which handle this functionality, so I don't have to roll out my own (and take care of edge cases, maintain it, not get command line auto complete... etc)

1 Answers1

0

I don't know of a tool that does exactly what you want. That doesn't mean it doesn't exist, but it does mean that it isn't something you can expect to find on a typical Linux desktop.

Copy-pasting a single file with ordinary household tools

If you're willing to change the requirements slightly, so that it isn't exactly the copy-cut-paste model, things can be simpler. One thing the copy-cut-paste model does which complicates the implementation, and is usually counter-intuitive for files anyway, is to take the cut-vs-copy decision at the point of origin. For files, it usually makes more sense to select at the point of origin, and copy or move at the point of destination. If you take this approach, the point of origin only needs to copy a list of file names, and not the decision to move or copy.

Copying a file name to the clipboard is fairly easy with common tools: xsel or xclip on X11, pbcopy and pbpaste on macOS. See Copy the contents of a file into the clipboard without displaying its contents and How to read over 4k input without new lines on a terminal?. xsel and xclip are usually not installed by default, but they're usually available as packages. I'll use xsel in my examples because it's shorter to type, the other tools are similar. xsel is actually buggy with large inputs, but this is not a problem with typical file names.

The following code examples assume that file names don't end in newline characters.

With zsh and xsel, here's a copy-paste example for a single file or directory:

/some/where% xsel <<<$PWD/myfile
/else/where% cp `xsel` .

With bash or ksh you need a few double quotes.

/some/where$ xsel <<<"$PWD/myfile"
/else/where$ cp -p "`xsel`" .

Note that pasting directly into a command line does not work if the file name contains shell special characters.

Dealing with multiple files

The easiest way to deal with multiple files is not to deal with multiple files. If you want to copy multiple files, copy the (name of the) destination directory from the destination terminal, and paste it into the source terminal.

/else/where$ xsel <<<"$PWD"
/some/where$ cp -Rp file1 file2 *.more.files "`xsel`"

I have a script called cfn (copy file name) which essentially copies its argument with $PWD/ prepended to the clipboard, but adds a bit of robustness: it supports multiple arguments, it copies to both the X selection and the clipboard, it works with both absolute and relative file names, it has options to support Windows paths. It could be improved to use null separators, and then it would support newlines in file names.

#! /bin/sh
# Copy file name

if [ "$#" -eq 0 ] || [ "$1" = "--help" ]; then cat <<EOF Usage: $0 [OPTION]... FILENAME [...] Copy FILENAME to both the X clipboard and the primary selection. If FILENAME is relative, prepent $PWD. Multiple FILENAMEs are separated by newlines, but no newline is added at the end.

-F Do not add a newline after the last file name (default) -f Do add a newline after the last file name -p PREFIX Prefix to use instead of $PWD -r ROOT Prefix to use before ROOT -s STRING Separator to use instead of a newline -u Use slash as the path separator (default) -w Use backslash instead of slash as the path separator EOF exit fi

set -e -f

append= cwd="$PWD/" explicit_cwd= names= nl=' ' root= sep='/'

change_separator () { if [ -z "$1" ]; then eval "$var=" return fi IFS=/ var=$1 eval "set $$var; $var=$1" shift if [ "$1" != "" ]; then set "$@" "" fi while [ "$#" -ne 0 ]; do eval "$var=$$var$sep$1" shift done }

while getopts Ffuwp:r:s: OPTLET; do case "$OPTLET" in F) append=;; f) append=1;; p) cwd="$OPTARG"; explicit_cwd=1;; r) root="$OPTARG";; s) nl="$OPTARG";; u) sep="/";; w) sep="\";; *) echo 1>&2 "$0: unknown option: -$OPTLET"; exit 3;; esac done shift "$((OPTIND-1))" if [ -z "$explicit_cwd" ] && [ "$sep" != "/" ]; then change_separator cwd fi

for x do case $x in .) x=${cwd%"$sep"};; [!/]) if [ "$sep" != "/" ]; then change_separator x fi x="$cwd$x";; ) if [ "$sep" != "/" ]; then change_separator x fi;; esac names="$names$root$x$nl" done if [ -z "$append" ]; then names=${names%"$nl"} fi printf %s "$names" | xsel printf %s "$names" | xsel -b

I don't have a corresponding paste script; here's how to paste multiple files (with newline-separated names) in zsh.

/else/where% cp -p ${(f)$(xsel)} .

In bash:

/else/where$ readarray -t a < <(xsel) && cp -p "${a[@]}" .
  • What I frequently do to easily get a file name from one terminal to another is realpath <file> (which prints the absolute path on a line by itself). Next, select the path by double- or triple- clicking that line. Switch to the other terminal and middle-click to paste (note that triple-clicking will result in a paste ending with a newline, so a middle-click will result in instant execution of whatever's been typed - generally harmless but not always. If in doubt I type a # (comment) then middle click to paste.) – Mark Aug 19 '20 at 00:20
  • @Mark The problem with pasting in a terminal is that it doesn't protect shell special characters, including mildly common ones such as space and '. That's why I recommend tools such as xsel. – Gilles 'SO- stop being evil' Aug 19 '20 at 07:02
  • Yeah, one of many problems caused by special chars. It's become habit for me to avoid them.

    I just realized OP doesn't specify OS; I'd assumed linux. I think the triple- and middle-click trick is exclusive to X11, so wouldn't work on OSX, and perhaps not with Wayland (etc) either.

    – Mark Aug 19 '20 at 20:14