3

I am trying to remove the filename extension from a list of files I've generated with the following bash script:

 #!/bin/bash
 file_list=$(find . -type f) #assuming that the files are stored in the same directory
 trimmed_file_list=$(printf '%s\n' "${file_list[@]%.*}")
 printf '%s\n' "${trimmed_file_list[@]}"

This expansion trims the extension off the last entry in the list, but not any of the earlier ones.

For example I want the following list:

 file1.pdf
 file2.pdf
 file3.png

To become

 file1
 file2
 file3

But instead I get:

 file1.pdf
 file2.pdf
 file3

I'd prefer not to do this in a loop, I'd like to use parameter expansion. I want to avoid cut because I only ever want to remove the last extension in the file.

There are a fair number of topics on parameter expansion and it appears that bash might be choking on find's use of newlines... I'm really not sure. If another of the pre-existing topics explains this issue, I don't fully grasp what's going on.

Topics that seem related but don't seem to solve my issue:

3
  • Your title mentions arrays, but there is no array in your code.
    – Kusalananda
    Aug 14, 2018 at 8:08
  • @Kusalananda Thank you! I've come to realize that since asking. Still learning :)
    – Shrout1
    Aug 14, 2018 at 13:04
  • @Kusalananda I've gone ahead and modified the question to more closely match the problem. That should prevent erroneous google searches from hitting the page. Thank you for the information!
    – Shrout1
    Aug 14, 2018 at 14:05

3 Answers 3

5

You could do all in one command.

find /path/to -type f -execdir bash -c 'printf "%s\n" "${@%.*}"' bash {} +
1
  • Thank you! I actually need to create a couple copies of the array, one for a menu that has the filename modified and one that will retain the original index value so that I can call the file from the menu. It is truly amazing how much bash can do though! The ability to embed everything into one command still blows me away! :D
    – Shrout1
    Aug 14, 2018 at 13:24
4

I believe you would want to make an array instead of a string:

IFS=$'\n' # split on newline only
set -o noglob # disable the glob part
file_list=($(find . -name '*.*' -type f))

Or with bash 4.4+, not breaking on file paths with newline characters:

readarray -td '' file_list < <(find . -name '*.*' -type f -print0)

Then your parameter expansion should work, though here it would make more sense to use an array variable again:

trimmed_file_list=("${file_list[@]%.*}")

In your code sample, you are making a string then asking parameter expansion to remove everything after the final dot character in the full string.

4
  • Thank you! I’ll test it as soon as I hit my desk in the morning.
    – Shrout1
    Aug 13, 2018 at 23:07
  • This assumes that no filenames in that directory contain any whitespace or globbing characters.
    – Wildcard
    Aug 14, 2018 at 3:48
  • @Wildcard, should be addressed by the latest edit. Aug 14, 2018 at 9:06
  • @Wildcard Thanks for the pointer! I'm generating the files that are dropped into this directory programmatically so it shouldn't be a problem.
    – Shrout1
    Aug 14, 2018 at 13:08
1

Your code does not include any arrays. Also, it puts a list of filenames into a string, $file_list. The contents of the string will end with file3.png and your parameter substitution removes .png from the string, leaving you with a single string of filenames where the last filename does not have a filename suffix.

Putting multiple separate objects (pathnames) into a single string automatically disqualifies the script from working properly for files whose names contains spaces (or whatever delimiter the string uses). Using an array would not help as you would still split the output of find on whitespaces.


To trim the extension off of all filenames of regular files in or below the current directory:

find . -type f -name "*.*" -exec sh -c '
    for pathname do
        printf "Would move %s to %s...\n" "$pathname" "${pathname%.*}"
        # mv -i "$pathname" "${pathname%.*}"
    done' sh {} +

This looks for regular files whose names contains at least one dot. The pathnames of these files are fed in batches into a small shell script that loops over them. The shell script renames the files by removing the last dot in the filename and everything that comes after. The actual mv command is commented out for safety.

The find command acts as a generator of pathnames for the internal shell script, and we are guaranteed to properly process pathnames containing spaces, newlines and tabs. There is no need for storing the output of commands in variables.

If you have an array of pathnames, possibly created by using

shopt -s globstar nullglob
file_list=( **/*.* )  # get pathnames of files with dots in their names

Then you would be able to output the pathnames without suffixes with

printf '%s\n' "${file_list[@]%.*}"

Whether this would help you, I don't know. If you want to use the pathnames with no suffixes for something, then using the output of the above printf command would be the wrong thing to do (you would be back at not handling strange pathname again). So when and how you delete the filename suffixes depends on what you'd like to do with the result.

Related:

1
  • Thank you! Your mastery of bash is very evident! I'm still scraping at the surface... Thank you for taking the time to help :)
    – Shrout1
    Aug 14, 2018 at 13:25

You must log in to answer this question.

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