1

Consider the problem that you want to rename a file called some/path/File With A Long Name.txt into some/path/File with a very long name.txt. To me, this was always one of the few situations, where working with the command line lacks the convenience of GUI-based file browsers, where you can simply "edit" the name of a file instead of passing both the old and the new name to mv.

As already answered for this question, the most convenient bash-based solution is to use brace expansion. However this is still not as convenient as simply editing the file name, especially in situations where you have a very long filename with spaces and you only want to change a few characters here and there.

My question is: Is there a way to rename files by "editing" the name somehow within the shell?

Disclaimer: Note that this is a self-answer-question. I came up with a solution that works for me, but I'm still open for any alternative idea.

5 Answers 5

1

I usually fall back to tab-completion and readline editing. However, here's a bash rewrite of your python script (requires version 4 I think for the fancy read options)

mvInPlace () {
    local path newpath
    for path in "$@"; do
        if [[ ! -f "$path" ]]; then
            echo "No such file: $path" >&2
            continue
        fi
        echo "Old path: $path"
        read -p "New path: " -i "$path" -e newpath
        mv -v "$path" "$newpath"
    done
}
1
  • Doesn't seem to work with zsh (not a bash user), but still nice to have a bash solution!
    – bluenote10
    Nov 7, 2014 at 16:36
1

With zsh a handy shortcut to duplicate the previous word (depending on your zle mode) is EscCtrl _ , that's the default in emacs mode. You can (re)define this yourself. e.g. just Ctrl_ which is good in a vi mode:

bindkey "^_" copy-prev-word
bindkey "^_" copy-prev-shell-word   # respects shell quote/escape

With the default bash readline bindings you can properly "edit" then execute a command line with Meta-X Meta-E. You might want to set the EDITOR or VISUAL variables to your preferred editor, which hopefully makes the task of duplicating a word easier.

You can nearly but not quite use the zsh copy-prev-word in bash, but its notion of what a word is won't always make you happy (strictly alphanumeric):

bind "\C-_: copy-backward-word"

then Ctrl _ CtrlY to invoke.

Newer bash versions (since 4.0) support shell words, but not for copy, only for motion/delete, so a slight hack is:

 bind "\C-_: shell-backward-kill-word"

The you can cut, then paste (yank) the word back twice with double CtrlY.

Bash also lets you bind a shell function, in which case you can do creative things by changing the variables READLINE_LINE and READLINE_POINT, but I don't have such a function to hand right now. Here's a toy function (bash-4.3 minimum) that either duplicates the last shell word, or if the last word appears to be a sed command applies it to the second last shell-word and replaces itself.

function _dupl() {
    [[ -z "${READLINE_LINE}" ]] && return                # empty, no action
    eval local -a aa=( "${READLINE_LINE}" ) 2>/dev/null  # split into words
    [[ ${#aa} -eq 0 ]] && return                         # parse problem

    if [[ "${aa[-1]:0:2}" == "s/" ]]; then               # sed 
        local _sed=$(sed -e "${aa[-1]}" <<< ${aa[-2]})
        unset aa[-1]                                     # remove last word
        printf -v READLINE_LINE "%q " "${aa[@]}"         # recreate line
        printf -v READLINE_LINE "%s%q" "${READLINE_LINE}" "$_sed"
    else
        printf -v READLINE_LINE "%s %q" "${READLINE_LINE}" "${aa[-1]}"
    fi
    READLINE_POINT=${#READLINE_LINE}   # move cursor to end
}
bind -x '"\C-_":"_dupl"'

Example:

mv "some/path/File With A Long Name.txt" s/txt/bak/Ctrl _

is then expanded on the input line to:

mv some/path/File\ With\ A\ Long\ Name.txt some/path/File\ With\ A\ Long\ Name.bak

(you'll note that quoted words become escaped since printf %q is used for safe expansion)

This last option is possibly over-stretching what one could consider "in a shell". if you have vim with the netrw script, you can simply edit the directory as a file:

  • vim .
  • use normal search/navigation features
  • use R to rename the file/directory at the cursor

See also: https://unix.stackexchange.com/questions/132235/quickest-way-to-rename-files-without-retyping-directory-path

1
  • 1
    Some very good ideas indeed.
    – bluenote10
    Nov 7, 2014 at 19:39
0

The rename utility is pretty useful in situations like changing case and such.

Otherwise I use tab-expansion for one-off edits:

mv File Tab File Tab

will produce:

mv File\ With\ A\ Long\ Name.txt File\ With\ A\ Long\ Name.txt which you can then edit on the command line as you'd like.

1
  • Yes, that is what I was doing so far as well. My main problem with this is that it is really inconvenient if you have to access a file in TAB some TAB subdirectory twice. I sometimes solved it by highlighting the first name and using the middle-mouse-button-paste for the second. I simply don't like doing things twice :).
    – bluenote10
    Nov 7, 2014 at 16:43
0

The solution I came up with is a simple Python script that relies on the functionality of the readline module. I use it like this: mvInPlace <PATH-TO-MOVE>. The script will now prompt for a new name/path, which is pre-filled with the old name/path. The readline module now gives the full editing capabilities, e.g., you can even move the cursor by CTRL + LEFT to jump to the next segment of the string. For the prompt, the given path is converted to an absolute path, which also makes it easy to move something to a different path.

#!/usr/bin/python

import os, sys, readline

if len(sys.argv) != 2:
  print "Usage: %s <PATH-TO-MOVE>" % os.path.basename(sys.argv[0])
  sys.exit(-1)

oldpath = os.path.abspath(sys.argv[1])
readline.set_startup_hook(lambda: readline.insert_text(oldpath))

print 'Old path: ' + oldpath
newpath = raw_input('New path: ')

cmd = 'mv -v "%s" "%s"' % (oldpath, newpath)
print "\n", cmd, "\n"
os.system(cmd)
0

I always just use tab as so:

file name here: test file long name.txt

so on command line

ren test tab will automagically fill in the full file name with escape chars already there right then hit space so I can begin the new name and again just typing test tab gives me the same file name again with escape characters so it is now

User@server:~/$ test\ file\ long\ name.txt test\ file\ long\ name.txt

and I can either just edit the few characters I need or if I wish to change the path I use cp instead of ren and just use <- to get to the space and add whatever new path I wish to move the file to. in *nix environs it does not matter how long the command is you can edit any part of it as long as you do not use / or escape to a new line in anyway it will just keep going to infinity. You could also edit the file name and THEN add the path after moving back to the space if you wish to not only change some of the name of the file but also to change it to a new path [of course you need cp and not ren and yes if you change the file's name you could even use cp in the same directory and not even use ren at all].

I do all that with no added anything so if you get adjusted to doing it this way even if you pull up to a friends machine you are not trying to like write your scripts on the fly or otherwise forget how to do it without the script when needed.

Hope that helps.

1
  • See my comment to @baochan's answer. This is all good as long as it does not take a lot of TAB's to complete the name (non-uniqueness or subdirectories). My question was how to avoid repeating the name at all.
    – bluenote10
    Nov 7, 2014 at 19:48

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .