mvredir

a unix shell command-line program

Move a web page and leave behind a redirect page to the new location. See also mkredir.

A redirect page allows a browser to get to a page at a new location even though the browser was told to get the page at the obsolete location. It is always a good idea to leave behind a redirect page at the old location when you move a page, so that links to the old location still work. This approach is preferable to a unix symbolic link (symlink) or “hard link” because those approaches proliferate different locations for the same page. Also, if the page moves to a different position in the hierarchy, relative links will not work when the page is accessed from a different path.

Examples:

1 Z% mvredir oldname.html newname.html
2 Z% mvredir *.html newdir

1. oldname.html will redirect to newname.html

2. name1.html will redirect to newdir/name1.html; olddir/name1.html will redirect to ../newdir/name1.html, etc.

The usage summary is this:

Usage:
  mvredir [ -f ] [ -b ] old new
  mvredir [ -f ] [ -b ] old ... directory

Move old to new, then create old containing HTML redirecting to new.

The -f option forces replacement of the target if it already exists.
The -b option causes the redirect file(s) to have a <body> that says:
     One moment while we redirect you to the page.
The old argument(s) must all be in the current directory.

This works only for redirecting html files. If you want to redirect a directory, you have to use the Apache RedirectPermanent directive (see the Apache Alias module (mod_alias) documentation). For example, I redirect from http://Yost.com/yostupload to its real location in a .htaccess file, like this:

RedirectPermanent /yostupload http://Yost.com/computers/yostupload

The presence of a <body> in the redirect page makes the redirect take longer and is necessary only for extremely old browsers.

Here’s the mvredir program:

#!/bin/zsh

# See notice of copyright and license at end.
  
commandName=${0##*/}

main() {
  
  doArgs "$@"
  
  umask og+rx
  
  if [[ $# == 2 && ! -d "$2" ]] ; then
    if ! move "$1" "$2" ; then
      exit 2
    fi
  else
    args=("$@")
    dir="$args[$#]"
    args[-1]=()
    for file in "${args[@]}"
    do
      if ! move "$file" "$dir/${file##*/}" ; then
        exit 2
      fi
    done
  fi
}

#-------------------------------------------------------------------------------

usage() {
if [[ $1 != '' ]] ; then echo 1>&2 "\n$1" ; fi
echo 1>&2 "
Usage: $commandName [ -f ] [ -b ] old new
or     $commandName [ -f ] [ -b ] file ... directory

Move old to new, then create old containing HTML redirecting to new.

The -f option forces replacement of the target if it already exists.
The -b option causes the redirect file(s) to have a <body> that says:
     One moment while we redirect you to the page
This version requires moving from the current directory, 
and the destination must be a relative path.
"
    exit 2
}

doArgs() {
  
  argForce=()
  argBody=()
  zparseopts -D -K - -help=argHelp f=argForce b=argBody
  
  case "$1" in
  -*)
    usage "Unknown option: $1"
    ;;
  esac
  
  case $# in
  0|1|)
    usage
    ;;
  2)
    ;;
  *)
    if [[ ! -d "$*[-1]" ]] ; then
      usage "$*[-1]: not a directory"
    fi
    ;;
  esac
  
  if [[ $#argHelp != 0 ]] ; then
    usage
  fi
}

entitize() {
  echo '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
        <html><head><title></title></head><body><a href="'"$1"'">x</a></body></html>' \
  | tidy -q 2> /dev/null \
  | sed -n '
      s,">x.*,,
      s,.*href=",,p
  '
  return 0
}

filter() {
  target=$(entitize "$1")
  sedArgs='s,^    ,,'
  if [[ $#argBody != 0 ]] ; then
    sedArgs="$sedArgs
      /<!-- /d
      / -->/d
    "
  fi
  targetEscaped=$(echo "$target" | sed 's,|,\\|,')
  sedArgs="$sedArgs
    s|__PLACEHOLDER__|$targetEscaped|
  "
  sed "$sedArgs"
  return 0
}

makeRedirectPage() {
  redirect="$1"
  destination="$2"
  if [[ -e "$destination" ]] ; then
    if [[ $#argForce == 0 ]] ; then
      echo 1>&2 "$commandName: ${destination}: File exists"
      return 2
    else
      rm -f "$destination"
    fi
  fi
  echo '<?xml version="1.0" encoding="iso-8859-1"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
            "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
      <meta http-equiv="content-type" content="text/html;charset=iso-8859-1" />
      <meta http-equiv="Refresh" content="0; URL=__PLACEHOLDER__" />
      <meta http-equiv="Expires" content="Tue, 1 Jan 1997 0:0:0 GMT" />
      <title>Redirect</title>
    </head>
    
    <!-- 
    <body bgcolor="#ffffff">
    
    <h3 align="center">
      <br />One moment while we redirect you to 
      <a href="__PLACEHOLDER__">the page</a>.</h3>
    
    </body>
     -->
    
    </html>
  ' \
  | filter "$redirect" \
  > "$destination"
  return $?
}

relativePath() {
  source="$1"
  destin="$2"
  # Output a relative path from source to dest.
  # Assume cwd is a/b/c/
  #  relativePath ../f1 dir/f2
  #   -> c/dir/f2
  #  relativePath ../dir1/f1 dir2/f2
  #   -> ../c/dir2/f2
  #  relativePath dir2/f1 ../f2
  #   -> ../../f2
  #  relativePath dir/f1 ../dir/f2
  #   -> ../../dir/f2
  #  relativePath ../dir1/f1 ../dir2/f2
  #   equivalent to dir1/f1 dir2/f2
  #  relativePath dir1/f1 dir2/f2
  #   -> ../dir2/f2
  # Also must handle full path cases
  
  # This needs a lot more work.  Too much punting.
  sourceDirPart=${source%/*}
  destinDirPart=${destin%/*}
  case "$destinDirPart" in
  '' | /*)
    echo 1>&2 "$commandName: In this version you can't use full paths."
    return 2
    ;;
  esac
  case "$sourceDirPart" in
  '' | /*)
    echo 1>&2 "$commandName: In this version you can't use full paths."
    return 2
    ;;
  "$source")
    echo "$destin"
    ;;
  *)
    echo 1>&2 "$commandName: In this version you can move from the current directory only."
    return 2
    ;;
  esac
  return 0
}

move() {
  old="$1"
  new="$2"
  if ! redirectPath="$(relativePath "$old" "$new")" ; then
    return 2
  fi
  if [[ -e "$new" ]] ; then
    if [[ $#argForce == 0 ]] ; then
      echo 1>&2 "$commandName: ${new}: File exists"
      return 2
    else
      rm -f "$new"
    fi
  fi
  /bin/mv $argForce "$old" "$new"
  makeRedirectPage "$redirectPath" "$old"
  return $?
}


TRAPINT() {
  echo 1>&2 "$commandName aborting: interrupted at file $file"
  exit 2
}

#-------------------------------------------------------------------------------

main "$@"

# Copyright 2005 Dave Yost <Dave@Yost.com>
# All rights reserved.
# This version is
#   mvredir 1.1 2006-07-20
# which at time of this publication can be found at:
#   http://Yost.com/computers/mvredir
# Redistribution and use in the form of source code or derivative data built 
# from the source code, with or without modification, are permitted provided 
# that the following conditions are met:
# 1. THE USER AGREES THAT THERE IS NO WARRANTY.
# 2. If and only if appropriate, the above phrase "This version is" must be 
#    followed by the phrase "a modified form of" or "extracted from" or 
#    "extracted and modified from".
# 3. Redistributions of source code must retain this notice intact.
# 4. Redistributions in the form of derivative data built from the source 
#    code must reproduce this notice intact in the documentation and/or other 
#    materials provided with the distribution, and each file in the derivative 
#    data must reproduce any Yost.com URI included in the original distribution.
# 5. Neither the name of Dave Yost nor the names of its contributors may be 
#    used to endorse or promote products derived from this software without 
#    specific prior written permission.
# 6. Written permission by the author is required for redistribution as part 
#    of a commercial product.
# This notice comprises all text from "Copyright" above through the end of 
# this sentence.

http://Yost.com/computers/mvredir/index.html - this page
2005-08-03 Created
2006-07-20 1.1 Usage help hopefully less confusing