6

I've a lot of files with the same name in different folders. How can I find all the paths and write them in a text file?

1
  • 4
    Do you know the file names that have been duplicated, or is this a more general "I know I've got some files with the same names, but I don't know their names" question? Commented Sep 12, 2018 at 8:33

5 Answers 5

9

This will handle the general case where you know that there are duplicate file names but you don't know what they are:

find -type f -print0 |
    awk -F/ 'BEGIN { RS="\0" } { n=$NF } k[n]==1 { print p[n]; } k[n] { print $0 } { p[n]=$0; k[n]++ }'

Within the awk script, we handle file paths that are NULL terminated (so we can process file names that might contain newlines), with $0 as the current file pathname. The variable n holds the file name component. k[] is a hash (keyed by n) that counts the number of occurrences of this file name, and p[] is another hash (also keyed by n) that holds the first corresponding full pathname.

Example

# Preparation
mkdir -p tmp/a tmp/b
touch tmp/a/xx tmp/a/yy tmp/b/yy tmp/b/zz

# Do it
find tmp -type f -print0 |
    awk -F/ 'BEGIN { RS="\0" } { n=$NF } k[n]==1 { print p[n]; } k[n] { print $0 } { p[n]=$0; k[n]++ }'

tmp/a/yy
tmp/b/yy

# Tidyup
rm -rf tmp

If you're using POSIX find - for example if you're on a Mac - then you won't have the -print0 option so the solution and command is slightly different. You need to specify an explicit starting directory. Also this version of find will not be able to handle NULL-terminated filenames so it will work only with file names that do not contain a newline character:

find . -type f -print |
    awk -F/ '{ n=$NF } k[n]==1 { print p[n]; } k[n] { print $0 } { p[n]=$0; k[n]++ }'
3
  • find: illegal option -- t
    – alper
    Commented Mar 8, 2023 at 17:10
  • 1
    @alper you're on a Mac? That doesn't use GNU find (it's not Linux) so the command is a little different. I've updated the question for what I assume is your use-case. Or you can install find from Homebrew Commented Mar 8, 2023 at 23:09
  • Yes sir I am on a Mac which find returns /usr/bin/find, looks like its built in. Doing brew install findutils and PATH=$(brew --prefix)/opt/findutils/libexec/gnubin:$PATH solved it.
    – alper
    Commented Mar 9, 2023 at 12:05
3

I would use find. Like so:

find <path> -type f -name <filename> > same_name.txt

Example:

find . -type f -name "foo" > same_name.txt
cat same_name.txt 
./dir_a/foo
./foo
./dir_b/foo
./tmp/foo

The above finds all files recursively, beginning from the current directory, with the name foo. The result is saved in the file same_name.txt

3
  • Thanks. But I need complete path. Is there any solution?
    – aortizma
    Commented Sep 12, 2018 at 8:42
  • 2
    @aortizma What's a "complete path"? A relative path is as complete as any other path.
    – Kusalananda
    Commented Sep 12, 2018 at 9:40
  • 2
    @aortizma Results from find begin with <path> you specify. If you mean you don't want relative paths then use an absolute path as <path>. Commented Sep 12, 2018 at 10:28
1

If installed, you can use locate.

locate filename

or save to a file:

locate filename > same_name.txt

To search a certain location only you can filter the results using grep:

locate filename | grep "/path/"
# e.g. search only in your /home folder
locate filename | grep "$HOME"

Note:

  • locate is much faster than find, because it performs a database search on a once-a-day scan of your hard drive.
  • It will not find files that you added today.
  • It will not find files in certain paths or file systems and mounts (run cat /etc/updatedb.conf to see what is excluded.)
1

Here's a complete script (I called it find-double-names.sh) that finds all file names that occur more than once. If you're doing this to clean up, please remember that files with the same name may have different content.

E.g. to find all double file names in your home directory, run it like this:

find-double-names.sh $HOME

You can pass more directories to include in the search, e.g.:

find-double-names.sh $HOME /usr/local /var/tmp

When you have a lot of files, obviously this script can take some time to run, and also require some disk space in /tmp.

#!/bin/bash

# This is the name of this script itself.
#
script="${0##*/}"

# The  arguments passed to this script are the parent
# directories to be searched, e.g: /home/me /usr/local
# Check if any given. If not, error out.
#
if [ -z "$1" ] ; then
    echo "Usage: $script <directory> [<directory>][...]" >&2
    exit 1
fi

# Create a temporary directory. For accurate results we need
# to be sure it is empty. This is one way to do this: create
# an temp dir that is garanteed to not exist yet.
#
# If you want to keep the "outputdir" with the results, make sure
# output dir you use does not contain files you want to keep, because
# files will be removed from it by this script! Better yet, make
# sure it is empty before starting this script.
#
outputdir=$(mktemp --tmpdir -d "${script}.XXXXXXXXXX")   # ensures new unique directory
trap "rm -r $outputdir" INT HUP QUIT ABRT ALRM TERM EXIT # ensures it is deleted when script ends

# Search the directories given as arguments, and process
# the paths of alle files one by one in a loop.
#
find "$@" -type f | while read path ; do
    filename="${path##*/}"
    echo "$path" >>"${outputdir}/${filename}.txt"
done

# Finally, if you want to end up with only file names that
# occur more than once, delete all output files that contain
# only one line.
#
for outputfile in $outputdir/*.txt ; do
    linecount=$(wc -l "$outputfile" | sed 's/ .*//')  # count lines in it
    if  [ "$linecount" = "1" ] ; then                 # if only one line
        rm "$outputfile"                              # remove the file
    fi
done

# Print the final result
#
for outputfile in $outputdir/*.txt ; do
    cat "$outputfile"
    echo               # empty line to separate groups of same file names
done
2
  • 1
    There are quoting issues in the script ($@ should be "$@" and other expansion should be quoted as well) and it won't handle certain filenames. Also, ${variable} is not needed and could be replaced by $variable throughout.
    – Kusalananda
    Commented Sep 12, 2018 at 8:46
  • 1
    True, thanks. Fixed 2 quoting issues. The ${var} instead of $var is a matter of taste, and I prefer ${var} when combining more than 1 var into a string.
    – Hkoof
    Commented Sep 12, 2018 at 8:53
0

The following bash script recursively finds all names of regular files (or symbolic links to regular files) that are duplicated in the top-level path given on the command line to the script (or in the current directory if no path is given).

At the end, a summary of each duplicated filename is given with a :-delimited list of directory names where the filename may be found.

#!/bin/bash

shopt -s globstar  # enable the ** glob
shopt -s dotglob   # also let patterns match hidden files

declare -A dirs    # where we store directories for each found name

for pathname in "${1:-.}"/**; do
    [ ! -f "$pathname" ] && continue  # not something we're interested in

    name=${pathname##*/}
    if [ -n "${dirs[$name]}" ]; then
        # we have seen this filename before
        dups+=( "$name" )
    fi

    # append directory name to ':'-delimited list for this filename
    dirs[$name]=${dirs[$name]:+"${dirs[$name]}:"}"${pathname%/*}"
done

# go through the list of duplicates and 
# print the found directory names for each
for name in "${dups[@]}"; do
    printf '%s:\n\t%s\n' "$name" "${dirs[$name]}"
done

Example run:

$ bash script.sh
somefile:
        ./a:./b
.profile:
        .:./t

The summary tells us that .profile is found in the current directory as well as in the directory t, and that somefile is found in the directories a and b.

You must log in to answer this question.

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