4

I have a directory located inside /var/www/html. I renamed this directory with mv from example1 to to example2.

How could I enter the newly named dir the moment the namechange was made?

How would you achieve this? Might do with find, mmin, 0*60?

I aim to rename and enter in one operation instead of two different operations.

Ideally, I would aim for a single-row solution and without customizing anything in the system.

7

You can add this in your .bashrc or .bash_aliases file (or equivalent if your shell is not bash):

mvcd () {
    mv -- "$1" "$2" &&
      cd -P -- "$2"
}

Then, restart your shell, then you can use the function like so:

mvcd foo bar

That assumes $2 is not an existing directory as otherwise mv would move $1 into it as opposed to to it (see the -T option of GNU mv to guard against that).

-- marks the end of options. mv "$1" "$2" would be mv "$option_or_source" "$option_or_argument_to_first_option_or_destination". mv -- "$1" "$2" guarantees that $1 and $2 are not treated as options even if their name starts with - so it's always treated as mv -- "$source" "$destination". Generally, you want to use -- wherever a command is given an arbitrary argument.

-P (for physical directory traversal) is to prevent the special processing that the cd builtin of POSIX shells do by default with .. path components so that it treats the content of $2 the same as mv did. Without it, in cdmv foo ../bar, cd could cd into a different bar directory from the one mv renamed foo as.

If you have set $CDPATH (or it was in the environment when the shell was started), you would also need to disable it for that one cd invocation:

mvcd () {
    mv -- "$1" "$2" &&
      CDPATH= cd -P -- "$2"
}

Some extra corner-case problems remain: - (and in some shells -2, +3) are treated specially even after --. If you want to rename foo to -, use mvcd foo ./- instead of mvcd foo -.

  • 1
    Thanks. I think you should edit and explain what was done here exactly. – JohnDoea Jun 12 '17 at 5:09
  • If I'm not wrong, it defining variables in bash.bashrc, than using them? Anyway, more elaboration is needed to achieve complete didactance. – JohnDoea Jun 12 '17 at 7:56
  • 2
    @Benia: What kind of elaboration do you think is necessary? I think it's quite simple enough, that any more would just muddy the main points. If you have any /specific/ aspect of the answer, that you want me to clarify, I can expand on that, otherwise I can add twenty pages of detailed expositions that still wouldn't address any of your specific doubts. You may want to read the shell documentation for more details of how shell function works. – Lie Ryan Jun 12 '17 at 9:01
  • I would add notes on what is the -- in the function, and saying what is the p and trying to say a phrase on what we actually putted on bash.bashrc (what the function does basically). – JohnDoea Jun 12 '17 at 9:10
  • 2
    @Benia, I was the one editing those in. See edit for details. – Stéphane Chazelas Jun 12 '17 at 11:25
3

In ksh93 and bash:

$ pwd
/tmp
$ mkdir test_dir
$ mv test_dir another_name
$ cd $_
$ pwd
/tmp/another_name

$_ expands to the last argument of the previous command.

As a shell function:

mvcd () {
    mv -- "$1" "$2"
    cd -P -- "$_"
}

But you may as well use

mvcd () {
    mv -- "$1" "$2" &&
    cd -P -- "$2"
}

as that would take care of not trying to change directory if the mv failed.

The double dash is necessary to allow names that start with a dash. The double dash will signal the end of command line options and prevent the name to be interpreted as an option, in those cases.

The -P with cd is necessary to make cd interpret paths in the same way as mv does ("physically" rather than "logically"). This avoids confusion when the new location is specified with a path that contains .. and traverses symbolic links.

If you move rather than just rename the directory, one would have to sort out the case where the move does not involve a renaming of the directory:

mv some_dir existing_dir

This would move some_dir into existing_dir so one would have to

cd existing_dir/some_dir

to change working directory to the moved directory afterwards.

The following modified shell function takes care of that:

mvcd () {
    if [ -d "$2" ]; then
        mv -- "$1" "$2" &&
        cd -P -- "$2/$1"
    else
        mv -- "$1" "$2" &&
        cd -P -- "$2"
    fi
}

or "shorter":

mvcd () {
    if [ -d "$2" ] && mv -- "$1" "$2"; then
        cd -P -- "$2/$1"
    elif mv -- "$1" "$2"; then
        cd -P -- "$2"
    fi
}

There is no way to combine the mv and cd into a truly atomic operation. The mv has to happen first, then the cd, no matter how you look at it, even if you wrote it in C. Doing one after the other (while checking the exit status of mv) is the correct way of doing it.

  • $_ is not specific to bash and ksh93. That probably comes from ksh (already there in ksh86) and is available in ash (since the start in 1989) and ash-based shells, pdksh-based shells and zsh. – Stéphane Chazelas Jun 12 '17 at 9:38
  • If moving to the same file system, you could very well do the cd before the mv (though many shells that do logical directory traversal would be confused as their $PWD would not be automatically updated) – Stéphane Chazelas Jun 12 '17 at 9:39
  • Note that mv (contrary to cd without -P) doesn't treat ".." path components specially, so you'd want to use the -P option to cd for cd to treat $2 the same as mv does. – Stéphane Chazelas Jun 12 '17 at 9:40
  • @StéphaneChazelas Thanks for the comments, as usual. Benia says he "renamed" the directory, not that he "moved" it. But -P should be there to be handle the case of a move as well, as you said. – Kusalananda Jun 12 '17 at 12:02
  • Note that this doesn't work if the target is an existing directory. – Gilles Jun 12 '17 at 12:29
2

When you rename a file (of type directory or other), you don't modify it¹, so the last modification time doesn't change. Its change status time (as checked by -cmin instead of -mmin) is updated though (at least on Linux, POSIX however gives no warranty on that).

Things that are modified are the directories you move the file from (as an entry is deleted/renamed in it) and to (as an entry is added/renamed in it). Their change status time will also have been modified.

So, if you wanted a heuristic to find the most recently renamed directory, an approach would be to find the file with the most recent ctime whose ctime is different from the mtime.

With zsh:

ctime_is_mtime() {
  zmodload zsh/stat
  local -A stat
  zstat -H stat -- $REPLY &&
    ((stat[ctime] == stat[mtime]))
}

cd ./**/*(D/oc^+ctime_is_mtime[1])

The glob qualifiers explained:

  • D don't skip hidden files and dirs
  • / only consider files of type directory
  • oc sort by ctime
  • ^ not
  • +ctime_is_mtime call the function to check ctime against mtime.

Note that time granularity is down to the second only even on file systems that support subsecond granularity.

Other cases where ctime is modified but not mtime include any modification to metadata except atime on access (like chmod, chown, setfacl...), and using touch to set an arbitrary modification time is another case where the ctime and mtime can end up being different.


¹ well technically, for a directory, when moved to a different directory, you do change its .. entry (which now points to the new parent directory) for those file systems that still store physical "." and ".." entries in directories instead of faking them at the OS level. However, AFAICT, systems still don't change the mtime in that case.

1

For completion's sake to attempt an answer without customizing anything in the system, you could do the following:

TARGET="tgt_name"; mv src_name ${TARGET}; cd ${TARGET}

Obviously you can choose a shorter variable name like A, but take note that this can overwrite variables in your current session. If you are worried about that, then consider setting the environment for a subshell:

env TARGET="tgt_name" bash -c 'mv src_name ${TARGET}; cd ${TARGET}'

At this point though, I think we've more than crossed the line of convenience versus complication, and it'd be much better to customize the system with the mvcd function from Lie Ryan's answer.

  • Note that this doesn't work if $TARGET is an existing directory. – Gilles Jun 12 '17 at 12:29
0

You can change the directory before calling mv. This leaves the shell confused about the current working directory. This is harmless for applications as long as the source and target are on the same filesystem, but it leaves the PWD variable, the pwd builtin and the prompt showing the old location, among others. To update pwd and friends, in zsh and bash, you can use cd .; but in other shells this does nothing and you need cd `pwd -P` instead; in both cases this does not retain any information about symbolic links.

cd /some/where
mv "$PWD" /else/where
cd .                     # works in bash and zsh; use cd "`pwd`" in other shells

You can leave out the quotes around $PWD if it doesn't contain any shell special characters. You can't use . as the source of the mv because . itself can't be moved, moving requires a “true” directory entry.

This solution has the advantage of not requiring to type any path more than once, and it's robust with respect to mv's two modes of operation, whether its second argument is an existing directory or not.

You can stuff this in a function, although the main benefit of this approach is that it involves little typing and doesn't benefit much from a function. If you do decide to use a function, I'd recommend one of the approaches below instead.


If you do things the other way round, a one-liner solution will have to sacrifice a lot of cases. The last argument to mv can either be an existing directory, in which case the source is moved into this directory and retains its base name, or not, in which case the final name is the target. Here's a function that handles both cases while remaining simple (which means that it misses a lot of edge cases).

mvcd () {
  mv "$@" && 
  if [ $# -eq 2 ] && [ ! -d "$2" ]; then
    cd "$2"
  else
    shift $(($#-2))
    cd "$2/${1##*/}"
  fi
}

I use a bigger, more robust function which sets the variable moved to the list of target files. It can still be fooled but you have to work harder. I've only ever bothered to write it for zsh, but it could be adapted to bash and ksh.

function mv {
  emulate -LR zsh
  setopt extended_glob
  local i target=
  moved=("$@")
  while [[ $moved[1] == -* ]]; do
    case $moved[1] in
      -t?*) target=${moved[1]#*t};;
      --t*=*) target=${moved[1]#*=};;
      -t|--t*) target=$moved[2]; shift moved;;
      -S|--su) shift moved;;
      --) break;;
    esac
    shift moved
  done
  if [[ -z $target ]]; then
    target=$moved[-1]
    moved=($moved[1,-2])
  fi
  target=${target%%/##}
  if [[ -d $target ]]; then
    for ((i=1; i<=$#moved; i++)); do
      moved[$i]=$target/${${moved[i]%%/##}##*/}
    done
  else
    moved=($target)
  fi
  command mv "$@"
}

Then, after an mv command, I can do things like ls -ld $moved, cd $moved, xdg-open $moved (that's what I use most), etc. For example:

mvcd () {
  mv "$@" && cd -- $moved[-1]
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.