Shell - modifying the find command output - shell

Is there anyway to print and output to a find command without listing the directory of the find in your output? meaning if I issue
find /home/people/name -type f -name >> /output/log/output.txt
the output in the log is written as:
/home/people/name/filename1.txt
/home/people/name/filename2.txt
/home/people/name/filename3.txt
what I want is the just the file name without the directory name? is that possible?

By default, as you saw, find prints full paths:
$ find /home/people/name -type f
/home/people/name/filename1.txt
/home/people/name/filename3.txt
/home/people/name/filename2.txt
find, however, does offer control over the output using -printf. To get just the filename, with no path, try:
$ find /home/people/name -type f -printf '%f\n'
filename1.txt
filename3.txt
filename2.txt
%f tells find that you want the filename without the path. \n tells find that you want a newline after each filename.
The output can, of course, be saved in a file:
$ find /home/people/name -type f -printf '%f\n' >output.txt
$ cat output.txt
filename1.txt
filename3.txt
filename2.txt

Related

List only files based on their middle part using bash ls command

I am a noob in bash and have a very basic question about bash.
I have files like:
a_lsst_z1.5_000.txt
a_lsst_z1.5_001.txt
a_lsst90_z1.5_001.txt
a_lsst_mono_z1.5_000.txt
a_lsst_mono_z1.5_001.txt
a_lsst_mono90_z1.5_000.txt
a_lsst_mono90_z1.5_001.txt
and so on
I would like to list ONLY files having lsst not (lsst90 or lsst_mono
or lsst_mono90.
I have tried:
ls a_lsst_*.txt # but it gives all files
Required output:
a_lsst_z1.5_000.txt
a_lsst_z1.5_001.txt
How to get only lsst files?
Maybe just match the first character after _ as a number?
echo a_lsst_[0-9]*.txt
After your edit, you could just match the z1.5 part:
echo a_lsst_z1.5_*.txt
try this
ls -ltr a_lsst_z1.5_*.txt
If you want to use ls and exclude certain character, you can try:
ls a_lsst[^9m]*.txt
This will exclude lsst90 and lsst_mono etc files.
find . -iname "a_lsst_*.txt" -type f -printf %P\\n 2>/dev/null
gives:
a_lsst_mono90_z1.5_001.txt
a_lsst_z1.5_000.txt
a_lsst_z1.5_001.txt
a_lsst_mono_z1.5_000.txt
a_lsst_mono_z1.5_001.txt
a_lsst_mono90_z1.5_000.txt
and
find . -iname "a_lsst_z1*.txt" -type f -printf %P\\n 2>/dev/null
gives:
a_lsst_z1.5_000.txt
a_lsst_z1.5_001.txt
with find command in the current dir . and with -iname you'll get expected results when using the pattern a_lsst_*.txt or a_lsst_z1*.txt.
Use -type f to get only match on files (not dirs).
Use -printf with %P to get paths without ./ at the beginning and \\n to have them ended with a new line char.
2>/dev/null prevents from displaying any error including very common Permission denied when using find command.

for all file names with .sh

it should display only the file names without the.sh
Example
$>./file.sh | cat -e
you can do :
find . -maxdepth 1 -type f -name \*.sh | sed 's|.sh$||'
remove the maxdepth 1 option if you want to find all the files recursively
You can just do ls | grep -v .sh to find in the current directory.
-v, --invert-match
Invert the sense of matching, to select non-matching lines.
If you need system-wide search, you'll need find:
find / -type f ! -name "*\.sh"
/ means root folder
type f = file
! -name = not sending with .sh

List only file names in directories and subdirectories in bash

I need to create a list of files in a directory and all of its subdirectories. I've used
find . -type f
to get the list of files, but I only need the file name, not the path that leads to it.
So instead of returning this:
./temp2/temp3/file3.txt
./temp2/file2.txt
./file.txt
I need to return
file3.txt
file2.txt
file.txt
find . -type f -exec basename {} \;
or better yet:
find . -type f -printf "%f\n"
You can use printf option in gnu find:
find . -type f -printf "%f\n"
For non-gnu find use -execdir and printf:
find . -type f -execdir printf "%s\n" {} +
find . -type f | xargs basename
The command basename strips the directories and outputs only the file name. Use xargs to chain the output of find as the input of basename.
Late answer :)
The -printf "%f" is the best solution, but the printf is available only in the GNU find. For example on the OS X (and probably FreeBSD too) will print:
find: -printf: unknown primary or operator
for such cases, myself prefer the following
find . -type f | sed 's:.*/::'
It is faster (on the large trees) as the multiple basename execution.
Drawback, it will handle only filenames without the \n (newline) in the filenames. For handling such cases the easiest (and most universal - but slow) way is using the basename.
You can also use perl, like:
perl -MFile::Find -E 'find(sub{say if -f},".")'

Terminal find, directories last instead of first

I have a makefile that concatenates JavaScript files together and then runs the file through uglify-js to create a .min.js version.
I'm currently using this command to find and concat my files
find src/js -type f -name "*.js" -exec cat {} >> ${jsbuild}$# \;
But it lists files in directories first, this makes heaps of sense but I'd like it to list the .js files in the src/js files above the directories to avoid getting my undefined JS error.
Is there anyway to do this or? I've had a google around and seen the sort command and the -s flag for find but it's a bit above my understanding at this point!
[EDIT]
The final solution is slightly different to the accepted answer but it is marked as accepted as it brought me to the answer. Here is the command I used
cat `find src/js -type f -name "*.js" -print0 | xargs -0 stat -f "%z %N" | sort -n | sed -e "s|[0-9]*\ \ ||"` > public/js/myCleverScript.js
Possible solution:
use find for getting filenames and directory depth, i.e find ... -printf "%d\t%p\n"
sort list by directory depth with sort -n
remove directory depth from output to use filenames only
test:
without sorting:
$ find folder1/ -depth -type f -printf "%d\t%p\n"
2 folder1/f2/f3
1 folder1/file0
with sorting:
$ find folder1/ -type f -printf "%d\t%p\n" | sort -n | sed -e "s|[0-9]*\t||"
folder1/file0
folder1/f2/f3
the command you need looks like
cat $(find src/js -type f -name "*.js" -printf "%d\t%p\n" | sort -n | sed -e "s|[0-9]*\t||")>min.js
Mmmmm...
find src/js -type f
shouldn't find ANY directories at all, and doubly so as your directory names will probably not end in ".js". The brackets around your "-name" parameter are superfluous too, try removing them
find src/js -type f -name "*.js" -exec cat {} >> ${jsbuild}$# \;
find could get the first directory level already expanded on commandline, which enforces the order of directory tree traversal. This solves the problem just for the top directory (unlike the already accepted solution by Sergey Fedorov), but this should answer your question too and more options are always welcome.
Using GNU coreutils ls, you can sort directories before regular files with --group-directories-first option. From reading the Mac OS X ls manpage it seems that directories are grouped always in OS X, you should just drop the option.
ls -A --group-directories-first -r | tac | xargs -I'%' find '%' -type f -name '*.js' -exec cat '{}' + > ${jsbuild}$#
If you do not have the tac command, you could easily implement it using sed. It reverses the order of lines. See info sed tac of GNU sed.
tac(){
sed -n '1!G;$p;h'
}
You could do something like this...
First create a variable holding the name of our output file:
OUT="$(pwd)/theLot.js"
Then, get all "*.js" in top directory into that file:
cat *.js > $OUT
Then have "find" grab all other "*.js" files below current directory:
find . -type d ! -name . -exec sh -c "cd {} ; cat *.js >> $OUT" \;
Just to explain the "find" command, it says:
find
. = starting at current directory
-type d = all directories, not files
-! -name . = except the current one
-exec sh -c = and for each one you find execute the following
"..." = go to that directory and concatenate all "*.js" files there onto end of $OUT
\; = and that's all for today, thank you!
I'd get the list of all the files:
$ find src/js -type f -name "*.js" > list.txt
Sort them by depth, i.e. by the number of '/' in them, using the following ruby script:
sort.rb:
files=[]; while gets; files<<$_; end
files.sort! {|a,b| a.count('/') <=> b.count('/')}
files.each {|f| puts f}
Like so:
$ ruby sort.rb < list.txt > sorted.txt
Concatenate them:
$ cat sorted.txt | while read FILE; do cat "$FILE" >> output.txt; done
(All this assumes that your file names don't contain newline characters.)
EDIT:
I was aiming for clarity. If you want conciseness, you can absolutely condense it to something like:
find src/js -name '*.js'| ruby -ne 'BEGIN{f=[];}; f<<$_; END{f.sort!{|a,b| a.count("/") <=> b.count("/")}; f.each{|e| puts e}}' | xargs cat >> concatenated

Find all files in a directory that are not directories themselves

I am looking for a way to list all the files in a directory excluding directories themselves, and the files in those sub-directories.
So if I have:
./test.log
./test2.log
./directory
./directory/file2
I want a command that returns: ./test.log ./test2.log and nothing else.
If you want test.log, test2.log, and file2 then:
find . -type f
If you do not want file2 then:
find . -maxdepth 1 -type f
If you need symlinks, pipes, device files and other specific elements of file system to be listed too, you should use:
find -maxdepth 1 -not -type d
This will list everything except directories.
using find is simple as:
find . -maxdepth 1 -type f
find only regular files
Use the -type f option with find to find only regular files. OR, to be even more-inclusive, use -not -type d to find all file types except directories.
When listing all files, I like to also sort them by piping to sort -V, like this:
# find only regular files
find . -type f | sort -V
# even more-inclusive: find all file types _except_ directories
find . -not -type d | sort -V
From man find:
-type c
File is of type c:
b - block (buffered) special
c - character (unbuffered) special
d - directory
p - named pipe (FIFO)
f - regular file
l - symbolic link; this is never true if the -L option or the -follow option is in effect, unless the symbolic link is broken. If you want to search for symbolic links when -L is in effect, use -xtype.
s - socket
D - door (Solaris)
To search for more than one type at once, you can supply the combined list of type letters separated by a comma , (GNU extension).
How to store the output of find (a multi-line string list of files) into a bash array
To take this one step further, here is how to store all filenames into a bash indexed array called filenames_array, so that you can easily pass them to another command:
# obtain a multi-line string of all filenames
filenames="$(find . -type f | sort -V)"
# read the multi-line string into a bash array
IFS=$'\n' read -r -d '' -a filenames_array <<< "$filenames"
# Now, the the variable `filenames_array` is a bash array which contains the list
# of all files! Each filename is a separate element in the array.
Now you can pass the entire array of filenames to another command, such as echo for example, by using "${filenames_array[#]}" to obtain all elements in the array at once, like this:
echo "${filenames_array[#]}"
OR, you can iterate over each element in the array like this:
echo "Files:"
for filename in "${filenames_array[#]}"; do
echo " $filename"
done
Sample output:
Files:
./file1.txt
./file2.txt
./file3.txt
References:
I was reminded of find . -type f from the main answer by #John Kugelman here.
I borrowed the part to read the multi-line string into a bash array from my own answer here: How to read a multi-line string into a regular bash "indexed" array
find . -type f
find /some/directory -type f
$ find . -type f -print
Each file will be on its own line. You must be in the directory you want to search.
One more option
ls -ltr | grep ^-
to list all files, while
ls -ltr | grep ^d
to list all directories

Resources