Linux- finding files with grep - bash

I'm new to Linux and I don't really know what I'm doing wrong.
I need to find files in catalog /usr/sbin that have 'fs' in their name and don't start with 'x'.
I need to write results in 111.txt file but cannot use find to do this.
I tried this command but it doesn't work.
grep -r -v '^x' -w 'fs' /usr/sbin/ > 111.txt

You could pipe find into two chained grep commands instead:
find /usr/sbin/ | grep 'fs' | grep -v '^/usr/sbin/x' > 111.txt

grep isn't meant for that, you should use find instead:
find /usr/sbin -type f -name '*fs*' -not -name 'x*' > 111.txt

Try
shopt -s extglob
printf '%s\n' /usr/sbin/#(fs*|[^x]*fs*) >111.txt
shopt -s extglob enables "extended globbing", which supports patterns like #(pattern1|pattern2). See the extglob section in glob - Greg's Wiki.
This answer originally suggested
# BAD CODE. DON'T USE.
printf '%s\n' /usr/sbin/[^x]*fs* >111.txt
That was broken because it excludes files whose names begin with fs.

Related

move files that no not contain specific string

I would like to move files using mv that do not contain the letter S in the filename. Could not find anyhting in the mv manual. Maybe combination with find or grep? It has to be case-sensitive.
input:
file1
fileS1
file2
fileS2
file to move:
file1
file2
You can do the selection in pure Bash without any extra software, if you enable extended globbing, which is off by default:
shopt -s extglob
mv !(*S*) /target/dir
For more information, search for extglob in the bash(1) manual page (the info is at the second match).
You could also use the Ignore-pattern switch from ls, like:
mv $(ls -I '*S*') /target/dir
You can use find with the -not flag for example.
find /path/to/source/dir -type f -not -name '*S*' \
| xargs mv -t /path/to/target/dir
GREP's -v flag can also be used here. According to the docs,
-v, --invert-match
Invert the sense of matching, to select non-matching lines.
Just use
ls | grep -v '*S*' | xargs mv -t target_dir/
Also, see this post.

list all the files that dont match pattern in bash

i have to search the files that don't have pattern:-
*abc*.txt and *xyz*.txt
Please suggest a way to list all the files which don't have the above patterns.
You can use an extended glob, such as the following:
!(*#(abc|xyz)*.txt)
In ksh, this works by default, whereas in bash you need to first enable a shell option:
shopt -s extglob
! negates the match and # matches any of the pipe-separated patterns.
This pattern expands to the list of files that don't match *abc*.txt or *xyz*.txt, so you can pass it to another command to see the result, e.g. printf:
printf '%s\n' !(*#(abc|xyz)*.txt)
With find command:
find -type f ! \( -name '*abc*.txt' -o -name '*xyz*.txt' \)
You can use the --hide=PATTERN option with ls. In your case it would be
ls --hide="*abc*.txt" --hide="*xyz*.txt"
I got the solution simple grep is working for me
ls | grep -v "abc" | grep -v "xyz"

Display configuration files in /etc that contain numbers in their names (using grep)

Task (12) from here.
Using grep I could solve this task as following:
grep --no-message -l [[:alnum:]] /etc/* | grep [[:digit:]]
Similar results were obtained:
ls -o /etc/ | grep ^- | awk '{print $8}' | grep [[:digit:]]
But I want to read all files under each directory, recursively. And this is what I could do:
grep --no-message -lR [[:alnum:]] /etc/* | grep [[:digit:]]
Also how this task can be solved today? Is this a correct way? What kind of additional solutions you can offer to solve this task?
You shouldn't be using to grep just to get file names. i.e. instead of what I think you are trying to do with
grep --no-message -l [[:alnum:]] /etc/*
You can just do
echo /etc/*
Since the goal is to list files with numbers, you can use a glob like:
echo /etc/*[[:digit:]]*
If you want to do this recursively you can do it in bash like:
shopt -s globstar
echo /etc/**/*[[:digit:]]*
Or you can use find:
find /etc -name '*[[:digit:]]*'
You can use find:
find /etc -type f -name \*[[:digit:]]\*
Please note that this command lists only files with digits in names, not the full paths. For the latter use a different filter:
find /etc -type f -path \*[[:digit:]]\*
Grisha and Andrey have given perfect answers. However, if you are looking for a solution that does use grep, here it is.
while read -r filepath; do
# grep for digits in the basename so that we choose only those
# files that have a digit in them, not in any parent directory name
if grep -q '[[:digit:]]' <<< "$(basename $filepath)"; then
echo "$filepath"
fi
done < <(find /etc -type f -print)
It's not efficient at all.
If the task is to have a pure grep solution, then here it is:
grep -laR . /etc | grep '^.*/[^/]*[[:digit:]][^/]*$'
The regular expression:
^...$ cover the full line;
.*/[^/]*...[^/]* look only for the file name.

Finding all commands excluding "."

So far I have this:
ls /usr/bin | grep "^[\.]"
The cmd still gets files with a "." in there.
I have looked at [[:punct:]] but still returns the same thing.
There's grep -v to exclude things. So try
ls /usr/bin | grep -v \\.
man grep says
-v, --invert-match
Selected lines are those not matching any of the specified patterns.
It's generally considered a bad idea to parse ls.
If I understand you correctly, you want all files in /usr/bin that don't have a dot in the name. You can use find to do that:
find /usr/bin -not -name "*.*"
It is more portable (thanks #Adrian) to use a ! instead of -not:
find /usr/bin ! -name "*.*"
Not really clear, what you want:
your command:
ls /usr/bin | grep "^[\.]"
mean, filter the output from ls to show only files, what are start with a dot.
grep "^[\.]"
^ ^^ - escaped dot
+- at the begining of the line
If you want, exclude all files what contains dot, use
ls /usr/bin | grep -v '\.' #or see HenrikN's answer and comments (grep -vF .)
it you want exclude only entries what are starting with dot, use
grep '^[^\.]'
whats mean anything, but dot at the start
Ps: anyway, parsing output form ls is usually an very bad idea. (http://mywiki.wooledge.org/ParsingLs)
You can change your regex to exclude files starting with ".":
ls -a /usr/bin | grep "^[^.]"
This regex selects only files which do not have "." at the start. By the way only ls -a shows files that starts with ".". How did you manage to get them without "-a" ?
This can be achieved with pure bash, if the extglob shell option is enabled.
shopt -s extglob
echo /usr/bin/!(*.*)
# or alternatively:
echo /usr/bin/+([!.])
You may replace echo with ls -d if you want to pipe the list to another command line-wise.
I think you are referring to the current working directory and parent dirctory and not a command with "a dot" in it.
Try this as you probably have ls aliased:
/bin/ls /usr/bin

How can I use inverse or negative wildcards when pattern matching in a unix/linux shell?

Say I want to copy the contents of a directory excluding files and folders whose names contain the word 'Music'.
cp [exclude-matches] *Music* /target_directory
What should go in place of [exclude-matches] to accomplish this?
In Bash you can do it by enabling the extglob option, like this (replace ls with cp and add the target directory, of course)
~/foobar> shopt extglob
extglob off
~/foobar> ls
abar afoo bbar bfoo
~/foobar> ls !(b*)
-bash: !: event not found
~/foobar> shopt -s extglob # Enables extglob
~/foobar> ls !(b*)
abar afoo
~/foobar> ls !(a*)
bbar bfoo
~/foobar> ls !(*foo)
abar bbar
You can later disable extglob with
shopt -u extglob
The extglob shell option gives you more powerful pattern matching in the command line.
You turn it on with shopt -s extglob, and turn it off with shopt -u extglob.
In your example, you would initially do:
$ shopt -s extglob
$ cp !(*Music*) /target_directory
The full available extended globbing operators are (excerpt from man bash):
If the extglob shell option is enabled using the shopt builtin, several extended
pattern matching operators are recognized.A pattern-list is a list of one or more patterns separated by a |. Composite patterns may be formed using one or more of the following sub-patterns:
?(pattern-list)
Matches zero or one occurrence of the given patterns
*(pattern-list)
Matches zero or more occurrences of the given patterns
+(pattern-list)
Matches one or more occurrences of the given patterns
#(pattern-list)
Matches one of the given patterns
!(pattern-list)
Matches anything except one of the given patterns
So, for example, if you wanted to list all the files in the current directory that are not .c or .h files, you would do:
$ ls -d !(*#(.c|.h))
Of course, normal shell globing works, so the last example could also be written as:
$ ls -d !(*.[ch])
Not in bash (that I know of), but:
cp `ls | grep -v Music` /target_directory
I know this is not exactly what you were looking for, but it will solve your example.
If you want to avoid the mem cost of using the exec command, I believe you can do better with xargs. I think the following is a more efficient alternative to
find foo -type f ! -name '*Music*' -exec cp {} bar \; # new proc for each exec
find . -maxdepth 1 -name '*Music*' -prune -o -print0 | xargs -0 -i cp {} dest/
A trick I haven't seen on here yet that doesn't use extglob, find, or grep is to treat two file lists as sets and "diff" them using comm:
comm -23 <(ls) <(ls *Music*)
comm is preferable over diff because it doesn't have extra cruft.
This returns all elements of set 1, ls, that are not also in set 2, ls *Music*. This requires both sets to be in sorted order to work properly. No problem for ls and glob expansion, but if you're using something like find, be sure to invoke sort.
comm -23 <(find . | sort) <(find . | grep -i '.jpg' | sort)
Potentially useful.
You can also use a pretty simple for loop:
for f in `find . -not -name "*Music*"`
do
cp $f /target/dir
done
In bash, an alternative to shopt -s extglob is the GLOBIGNORE variable. It's not really better, but I find it easier to remember.
An example that may be what the original poster wanted:
GLOBIGNORE="*techno*"; cp *Music* /only_good_music/
When done, unset GLOBIGNORE to be able to rm *techno* in the source directory.
My personal preference is to use grep and the while command. This allows one to write powerful yet readable scripts ensuring that you end up doing exactly what you want. Plus by using an echo command you can perform a dry run before carrying out the actual operation. For example:
ls | grep -v "Music" | while read filename
do
echo $filename
done
will print out the files that you will end up copying. If the list is correct the next step is to simply replace the echo command with the copy command as follows:
ls | grep -v "Music" | while read filename
do
cp "$filename" /target_directory
done
One solution for this can be found with find.
$ mkdir foo bar
$ touch foo/a.txt foo/Music.txt
$ find foo -type f ! -name '*Music*' -exec cp {} bar \;
$ ls bar
a.txt
Find has quite a few options, you can get pretty specific on what you include and exclude.
Edit: Adam in the comments noted that this is recursive. find options mindepth and maxdepth can be useful in controlling this.
The following works lists all *.txt files in the current dir, except those that begin with a number.
This works in bash, dash, zsh and all other POSIX compatible shells.
for FILE in /some/dir/*.txt; do # for each *.txt file
case "${FILE##*/}" in # if file basename...
[0-9]*) continue ;; # starts with digit: skip
esac
## otherwise, do stuff with $FILE here
done
In line one the pattern /some/dir/*.txt will cause the for loop to iterate over all files in /some/dir whose name end with .txt.
In line two a case statement is used to weed out undesired files. – The ${FILE##*/} expression strips off any leading dir name component from the filename (here /some/dir/) so that patters can match against only the basename of the file. (If you're only weeding out filenames based on suffixes, you can shorten this to $FILE instead.)
In line three, all files matching the case pattern [0-9]*) line will be skipped (the continue statement jumps to the next iteration of the for loop). – If you want to you can do something more interesting here, e.g. like skipping all files which do not start with a letter (a–z) using [!a-z]*, or you could use multiple patterns to skip several kinds of filenames e.g. [0-9]*|*.bak to skip files both .bak files, and files which does not start with a number.
this would do it excluding exactly 'Music'
cp -a ^'Music' /target
this and that for excluding things like Music?* or *?Music
cp -a ^\*?'complete' /target
cp -a ^'complete'?\* /target

Resources