1

I want to move veryverylongfilename.txt to a filesystem which has a short NAME_MAX.

mv veryverylongfilename.txt /mnt/tiny gives me an ENAMETOOLONG-type error:

mv: cannot stat '/mnt/tiny/veryverylongfilename.txt': File name too long

What command should I use instead, to truncate the filename if necessary?

It would be great if the command could keep the extension. Also, it would be nice to avoid overwriting existing files, for instance when moving veryverylongfilename1.txt then veryverylongfilename2.txt, by using any kind of unique identifier in place of the last few characters before the extension.

  • 3
    What schemes do you have in mind to come up with unique file names in the event of collisions? – Jeff Schaller Aug 15 '17 at 12:15
  • @JeffSchaller: Anything is fine, for instance replacing X characters with an incremental integer. – Nicolas Raoul Aug 15 '17 at 12:25
  • One attempt: generate a hash out of the trunkened filename, do a base64 of that hash and take as many chars of that resulting string as allowed in length. You can calculate how rare random collisions are that way. Remember to replace the / of base64 by another char like _. – Philippos Aug 15 '17 at 12:29
2

The following function (tested in bash) will attempt to move its first parameter to its second parameter. It expects (and tests for) the first parameter to be a file and its second to be a directory.

The local "namemax" variable should be adjusted to your filesystem's NAME_MAX.

moveshort() {
  local namemax=8

  # simple sanity checks
  [ "$#" -eq 2 ] || return 1
  local src=$1
  [ -e "$src" ] || return 2
  local dest=$2
  [ -d "$dest" ] || return 3

  local extension=${src##*.}
  local basename=${src%.*}
  # the base name has ($namemax - $extension - 1)
  # characters available to it (1 for the period)
  local maxbase=$((namemax - ${#extension} - 1))

  # shorten the name, if necessary
  basename=${basename:0:maxbase}

  # echo "Shortened name: ${basename}.${extension}"
  # find a new name, if necessary
  if [ -e "${dest}/${basename}.${extension}" ]
  then
    local index=1
    local lenindex=${#index}
    #local newbase=${basename:0:-lenindex}
    local newbase=${basename:0:maxbase - lenindex}
    # loop as long as a conflicting filename exists and
    # we're not out of space in the filename for the index
    while [ -e "${dest}/${newbase}${index}.${extension}" -a "${#index}" -lt "$maxbase" ]
    do
      index=$((index + 1))
      lenindex=${#index}
      newbase=${newbase:0:maxbase - lenindex}
    done
    if [ -e "${dest}/${newbase}${index}.${extension}" ]
    then
      echo "Failed to find a non-colliding new name for $src in $dest" >&2
      return 4
    fi
    basename=${newbase}${index}
    # echo "new name = ${basename}.${extension}"
  fi

  # perform the move
  mv -- "$src" "${dest}/${basename}.${extension}"
}

After the sanity-checks, the function saves off the extension and remaining base filename, then determines how many characters are available for the base filename to use.

If the given filename is already too long, then we chop off the extra characters.

If the shortened name already exists in the destination, then we begin looping, starting at 1, generating a new base filename until we run out of space in the base filename or we find a file that doesn't exist. The new base filename gets squeezed by the index as the index grows.

If we run out of space in the filename, the function echoes out an error and returns; otherwise, it attempts to execute the mv.

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.