Chitholian
Chitholian

Reputation: 472

How to remove question mark and the rest from file names?

I have grabbed a whole website template using wget. It created many asset files in different subdirectories where filenames contain question marks, ./fonts/fontawesome-webfont.woff?v=4.5.0 for example. I need to truncate ? and the rest from these filenames. I tried this command:

find . -type f -name '*\?*' -exec mv "{}" "$(echo {} | sed 's/\?.*//g')" \;

But it does not work; nothing is replaced/removed, it shows this error:

mv: './fonts/fontawesome-webfont.woff?v=4.5.0' and './fonts/fontawesome-webfont.woff?v=4.5.0' are the same file

I have these type of filenames in different directories, so I tried find command. What am I missing here ?

Upvotes: 1

Views: 3668

Answers (4)

oguz ismail
oguz ismail

Reputation: 50795

Here is a bulletproof one.

find . -name '*\?*' -type f -exec sh -c '
for fp; do
  fn=${fp##*/}
  echo mv "$fp" "${fp%/*}/${fn%%\?*}"
done' sh {} +

The loop above isolates the name of each selected file (in the variable named fn) in order to be able to remove the leftmost question mark and the rest from it without breaking anything; if we did ${fp%%\?*} instead, we couldn't process the contents of a directory with a question mark in its name. Though if we knew for sure that each file name contains only one question mark, ${fp%\?*} would work fine.

Remove echo if the output looks good.

Upvotes: 6

Wiktor Stribiżew
Wiktor Stribiżew

Reputation: 627126

Edit

A variation of Oguz ismail's solution:

find . -name '*\?*' -type f -exec sh -c 'for fp; do echo mv -- "$fp" $(printf "%s" "$fp" | sed "s,?[^/]*$,,"); done' sh {} +
## or
find . -name '*\?*' -type f -exec sh -c 'for fp; do echo mv "$fp" $(echo "$fp" | sed "s,?[^/]*$,,"); done' sh {} +

The find command searches for files (-type f) that contain a ? char anywhere in the file name (-name '*\?*') starting from the current folder (.), and once the file is found the sh script is executed. The script is for fp; do echo mv -- "$fp" $(printf "%s" "$fp" | sed "s,?[^/]*$,,"); done, and it iterates over the filepaths (fp) found, and moves (renames) the filepaths with the same filepath without the part beginning with the first ? char found in the file path.

The sed "s,?[^/]*$,," command actually finds a ?, then any amount of chars other than / till the end of string, and removes this part from the file path (so, it only removes the part of a file name, folder names can contain ? chars).

The -- after mv are necessary in case a filepath starts with a - char.

NOTE: Remove the echo from the above command to actually rename the files.

Linux 18.04 tested solution with perl-rename

You can use

find . -depth -type f -exec rename -n -v 's/\?.*//' {} +

Check the results and if they are as expected remove -n that is meant for a "dry" run to only check the command workings. Here, -v ("verbose" option) is used to print the names of files that are successfully renamed.

You can read about how to install and use this rename command here.

Upvotes: 0

markp-fuso
markp-fuso

Reputation: 35106

Assuming rename is not available (or you're unable to get it installed), I'd probably opt for something a bit more straight forward and easier (?) to understand/maintain.

Suppose I have the following under a directory named question:

$ ls -1 question
abc?v=4.5.0
def.txt
ghi.pdf?v=1.4.3

One idea using a simple while loop and parameter substitution:

find . -type f -name '*\?*' |
while read -r fname
do
    echo mv "${fname}" "${fname//\?*/}"
done

This generates:

mv ./question/abc?v=4.5.0 ./question/abc
mv ./question/ghi.pdf?v=1.4.3 ./question/ghi.pdf

Once you're satisfied with the generated mv commands the echo can be removed in order to actually run the mv commands.

Upvotes: 1

0stone0
0stone0

Reputation: 44102

Please Consider @Wiktor's answer, this answers purely focuses on the find command


As stated in the comments, the sed is called before find finds it files. We can work around this using -exec bash -c and pass the pathnames ({}) as argument:

find . -type f  -exec bash -c 'echo mv $0 $(sed "s/\?.*//g" <<< $0)' "{}" \;

If the output seems find for each file, remove the echo prefix to mv the files:

find . -type f  -exec bash -c 'mv $0 $(sed "s/\?.*//g" <<< $0)' "{}" \;

Small example on my local machine:

$ ls -lt
total 0
-rw-r--r-- 1 me wheel 0 Jun  3 17:32 'something.exe?v=23456'
-rw-r--r-- 1 me wheel 0 Jun  3 17:32 'myrandom_file?x=123'
-rw-r--r-- 1 me wheel 0 Jun  3 17:32 'fontaweietsanderssome-webfont.woff?v=4.5.0'
$
$ find . -type f  -exec bash -c 'mv $0 $(sed "s/\?.*//g" <<< $0)' "{}" \;
$
$ ls -lt
total 0
-rw-r--r-- 1 me wheel 0 Jun  3 17:32 something.exe
-rw-r--r-- 1 me wheel 0 Jun  3 17:32 myrandom_file
-rw-r--r-- 1 me wheel 0 Jun  3 17:32 fontaweietsanderssome-webfont.woff
$

Upvotes: 1

Related Questions