15

Can someone give me a command that would:

  • move a file towards a new directory
  • and leave a symlink in its old location towards its new one
16

mv moves a file, and ln -s creates a symbolic link, so the basic task is accomplished by a script that executes these two commands:

#!/bin/sh
mv -- "$1" "$2"
ln -s -- "$2" "$1"

There are a few caveats. If the second argument is a directory, then mv would move the file into that directory, but ln -s would create a link to the directory rather than to the moved file.

#!/bin/sh
set -e
original="$1" target="$2"
if [ -d "$target" ]; then
  target="$target/${original##*/}"
fi
mv -- "$original" "$target"
ln -s -- "$target" "$original"

Another caveat is that the first argument to ln -s is the exact text of the symbolic link. It's relative to the location of the target, not to the directory where the command is executed. If the original location is not in the current directory and the target is not expressed by an absolute path, the link will be incorrect. In this case, the path needs to be rewritten. In this case, I'll create an absolute link (a relative link would be preferable, but it's harder to get right). This script assumes that you don't have file names that end in a newline character.

#!/bin/sh
set -e
original="$1" target="$2"
if [ -d "$target" ]; then
  target="$target/${original##*/}"
fi
mv -- "$original" "$target"
case "$original" in
  */*)
    case "$target" in
      /*) :;;
      *) target="$(cd -- "$(dirname -- "$target")" && pwd)/${target##*/}"
    esac
esac
ln -s -- "$target" "$original"

If you have multiple files, process them in a loop.

#!/bin/sh
while [ $# -gt 1 ]; do
  eval "target=\${$#}"
  original="$1"
  if [ -d "$target" ]; then
    target="$target/${original##*/}"
  fi
  mv -- "$original" "$target"
  case "$original" in
    */*)
      case "$target" in
        /*) :;;
        *) target="$(cd -- "$(dirname -- "$target")" && pwd)/${target##*/}"
      esac
  esac
  ln -s -- "$target" "$original"
  shift
done
  • 1
    Thanks Gilles, for the script and the explanations. I'll try to understand that ! – Yo B. Sep 8 '15 at 14:33
  • This should be marked as the right answer – Sridhar Sarnobat Dec 30 '16 at 16:06
  • To make this answer even more awesome, rsync --remove-source-files might be more informative to users who are moving a large file off a disk which may take some time. – Sridhar Sarnobat Dec 30 '16 at 16:17
  • I think you should call sh with -e so it breaks on errors, for example if you stop the mv command because a file already exists – Alex Sep 5 '18 at 19:30
2

Put this in .sh file and make it executable(chmod +x filename):

#!/bin/bash

mv "$1" "$2"
ln -s "$2" "$1"

Usage example:

./test.sh asdf otherdir/asdf

Note that this does no safety checks, etc. Depending on how complex is your task, this might be sufficient.

  • Thank you Matthew, this works perfectly when applied on one file. But can you help me generalising this to two directories like this : ./test.sh .mozila/firefox/zotero/*/*.pdf MyBbliography/*.pdf. The * does not seem to be working with your test.sh. Have you got a workaround ? Thx – Yo B. Sep 7 '15 at 12:04
  • 2
    @kadok MatthewRock's script only works for a single file, and the target must be an absolute path or else the source must be in the current directory. – Gilles 'SO- stop being evil' Sep 7 '15 at 21:28
  • Also, it won't work if $1 is a file but $2 is a directory. – Sridhar Sarnobat Dec 30 '16 at 16:04
0

I generally use the this one-line function:

function ml() { mkdir -p "$(dirname "$1")" && rsync -aP --no-links "$1" "$2" && ln -sf "$2" "$1" }

Usage is similar to mv or cp:

ml old_file target_dir/new_file_name

Breaking it down:

  • mkdir -p "$(dirname "$1")" - create the destination directory if it does not already exist
    • dirname "$1" - get the directory component of the path (strip the filename)
  • rsync -aP --no-links "$1" "$2" - copy the file over to the destination. Replace this with mv "$1" "$2" if both files are on the same filesystem for better performance.
    • -a - preserve ownership and all other permissions. You can tune this to only preserve the components you want.
    • -P - show progress.
    • --no-links - don't copy links--this means you can run this command as many times as you want on the same file, and you won't ever lose your file by accidentally overwriting your destination file with a symlink to itself.
  • ln -sf "$2" "$1" - overwrite the old file with a symlink to the new file
    • -s - use symbolic links
    • -f - overwrite the old file

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.