Wildcard expansion - searching for one in a set of possibilities - bash

I could have sworn you could do the following:
ls *.{java, cpp}
but that does not seem to work. I know this answer is probably on the site somewhere but I couldn't find it via search.
For instance, if I want to be able to use the globbing with a find command, I would want to do something like
find . -name "*.{java,cpp}" | xargs grep -n 'TODO'
Is this possible without resorting to using the -o binary operator?

It is likely that you are seeing an error message such as this:
ls: cannot access *.{java: No such file or directory
ls: cannot access ,cpp}: No such file or directory
If that's the case, it's because of the space after the comma. Leave it out:
ls *.{java,cpp}
For future reference, it is more helpful to post error messages than to say "it's not working" (please don't take this personally. It's meant for everyone to see. I even do it sometimestoo often).

ls *.{java,cpp} works just fine for me in bash...:
$ ls *.{java,cpp}
a.cpp ope.cpp sc.cpp weso.cpp
helo.java qtt.cpp srcs.cpp
Are you sure it's not working for you...?
find is different, but
find -E . -regex '.*\.(java|cpp)'
should do what you want (in some versions you may not need the -E or you may need a -regextype option there instead, "man find" on your specific system to find out).

But this does work in Bash:
$ ls
a.h a.s main.cpp main.s
$ ls *.{cpp,h}
a.h main.cpp
Are you sure you're in Bash? If you are, maybe an alias is causing the issue: try /bin/ls *.{java,cpp} to make sure you don't call the aliased ls.
Or, just take out the spaces in your list inside the {} -- the space will cause an error because Bash will see *.{java, as one argument to ls, and it will see cpp} as a second argument.

For your particular example, this may also do what you want
grep -rn TODO . --include '*.java' --include '*.cpp'

Related

Alias for a combination of grep and find is needed

Many times I need to search from a directory and below for a pattern in all files with a specific type. For example, I need to ask grep not to look into files other than *.h, *.cpp or *.c. But if I enter:
grep -r pattern .
it looks into all files. If I enter:
grep -r pattern *.c
it tries *.c files in the current folder (no file in my case) and files in *.c folders (no folder in my case). I want to ask it too look into all folders but only into file with the given type. I think grep is not enough to be used for this purpose. So, I get help from find too, like this:
grep pattern `find . -name '*c'`
First, let me know whether I'm right about getting help from find. Can grep be enough? Second, I prefer to write an alias for bash to be used like this:
mygrep pattern c
to be translated to the same command avoiding usage of ` and ' and be simpler. I tried:
alias mygrep="grep $1 `find . -name '*$2'`"
But it doesn't work and issues an error:
grep: c: No such file or directory
I tried to change it, but I couldn't succeed to a successful alias.
Any idea?
This would be better done as a function than an alias, and using -exec instead of passing the output of find to grep. That output would be subject to word splitting and globbing, so could produce surprising results as is. Instead try:
mygrep () {
find . -name "*$2" -exec grep "$1" {} +
}

Using ? wildcard with ls

I'm trying to use the ? wildcard to display only 1 character files, and ?.* to display 1 character files with extensions.
what works:
cd /mydir
ls ? ?.*
I'm trying to use this in a shell script so therefor i cant use "cd"
What i'm trying to get to work
ls ? ?.* /mydir
and it gives me the output:
ls: cannot access ?.*: No such file or directory
I've also tried:
ls /mydir ? ?.*
which gives me the exact same output as before.
From a comment you wrote:
im in college for linux administrator and 1 of my current classes in shell scripting. My teacher is just going over basic stuff. And, my current assingment is to get the number of files in the tmp directory of our class server, the number of files that end in .log and the number of files that only have 1 character names and store the data in a file and then display the stored data to the user. I know it's stupid, but it's my assignment.
I only hope that they don't teach you to parse the output of ls in college... it's one of the most terrible things to do. Please refer to these links:
Why you shouldn't parse the output of ls(1)
Don't ever do these
The solution you chose
ls /mydir/? /mydir/?.* | wc -l
is broken in two cases:
If there are no matching files, you'll get an error. You can fix that in two ways: use shopt -s nullglob or just redirect stderr to devnull.
If there's a newline in a file name. Try it: touch $'a.lol\nlol\n\lol\nlol\nlol'. LOL.
The proper bash way is the following:
shopt -s nullglob
shopt -u failglob
files=( /mydir/? /mydir/?.* )
echo "There are ${#files[#]} files found."
When you write ls ? ?.* /mydir, you're trying to display the files matching three distincts patterns: ?, ?.*, and /mydir. You want to match only /mydir/? and /mydir/?.*, hence this command: ls /mydir/? /mydir/?.*.
Edit: while this is a correct answer to the initial question (listing /mydir/? and /mydir/?.*), OP wanted to do this to parse the output and get the file count. See #gniourf_gniourf's answer, which is a much better way to do this.
cd works perfectly within a shell script, use it. For minimal impact on the script, I would use a subshell:
( cd /mydir && ls ? ?.* )
That way, you don't change the current working directory of the script (and neither $OLDPWD, which would be clobbered with cd /mydir; ...; cd -;).
While ls seems like an obvious choice, find is probably more suitable:
find /mydir \! -name "." -a \( -name "?" -o -name "?.*" \)

Copying files with specific size to other directory

Its a interview question. Interviewer asked this "basic" shell script question when he understand i don't have experience in shell scripting. Here is question.
Copy files from one directory which has size greater than 500 K to another directory.
I can do it immediately in c lang but seems difficult in shell script as never tried it.I am familiar with unix basic commands so i tried it, but i can just able to extract those file names using below command.
du -sk * | awk '{ if ($1>500) print $2 }'
Also,Let me know good shell script examples book.
It can be done in several ways. I'd try and use find:
find $FIRSTDIRECTORY -size +500k -exec cp "{\} $SECONDDIRECTORY \;
To limit to the current directory, use -maxdepth option.
du recurses into subdirectories, which is probably not desired (you could have asked for clarification if that point was ambiguous). More likely you were expected to use ls -l or ls -s to get the sizes.
But what you did works to select some files and print their names, so let's build on it. You have a command that outputs a list of names. You need to put the output of that command into the command line of a cp. If your du|awk outputs this:
Makefile
foo.c
bar.h
you want to run this:
cp Makefile foo.c bar.h otherdirectory
So how you do that is with COMMAND SUBSTITUTION which is written as $(...) like this:
cd firstdirectory
cp $(du -sk * | awk '{ if ($1>500) print $2 }') otherdirectory
And that's a functioning script. The du|awk command runs first, and its output is used to build the cp command. There are a lot of subtle drawbacks that would make it unsuitable for general use, but that's how beginner-level shell scripts usually are.
find . -mindepth 1 -maxdepth 1 -type f -size +BYTESc -exec cp -t DESTDIR {}\+
The c suffix on the size is essential; the size is in bytes. Otherwise, you get probably-unexpected rounding behaviour in determining the result of the -size check. If the copying is meant to be recursive, you will need to take care of creating any destination directory also.

How to rename files in current directory and its subdirectories using bash script?

I'm able to use the 'rename' command to add the missing character to all filenames in the current directory like this:
echo "Renaming files..."
rename -v "s/^abcd124(.+)/abcd1234$1/" *.wav.gz;
echo "Done."
However, I'd like to do this for the current directory and all its subdirectories. I tried this:
echo "Renaming files..."
for dir in $(find ./ -type d); do
rename -v "s/^$dir\/abcd124(.+)/$dir\/abcd1234$1/" *.wav.gz;
done;
echo "Done."
However, if the $dir variable contains any of these special characters: {}[]()^$.|*+?\ then they are not escaped properly with \ and my script fails.
What would be the best way to solve this problem? Also, what do you guys think of using awk to solve this problem (advantages/disadvantages?)
You can also try:
find . -name "*.wav.gz" | xargs rename -v "s/abcd124*/abcd1234$1/"
It works on newer Unix systems with "xargs" command available. Note that I edited the regular expression slightly.
Try:
find ./ -type d -execdir rename -v "s/^abcd124(.+)/abcd1234\1/" *.wav.gz ";"
Find does already provide an iterator over your files - you don't need for around it or xargs behind , which are often seen. Well - in rare cases, they might be helpful, but normally not.
Here, -execdir is useful. Gnu-find has it; I don't know if your find has it too.
But you need to make sure not to have a *.wav.gz-file in the dir you're starting this command, because else your shell will expand it, and hand the expanded names over to rename.
Note: I get an warning from rename, that I should replace \1 with $1 in the regex, but if I do so, the pattern isn't catched. I have to use \1 to make it work.
Here is another approach. Why at all search for directories, if we search for wav.gz-files?
find . -name "*.wav.gz" -exec rename -v "s/^abcd124(.+)/abcd1234\1/" {} ";"
In bash 4:
shopt -s globstar
rename -v "s/^$dir\/abcd124(.+)/$dir\/abcd1234$1/" **/*.wav.gz;
Just be aware that Gentoo Linux has its rename utility points to
http://developer.berlios.de/project/showfiles.php?group_id=413
by its ebuild
http://sources.gentoo.org/cgi-bin/viewvc.cgi/gentoo-x86/sys-apps/rename/rename-1.3.ebuild?view=markup
for Debian or maybe Ubuntu,
rename is /usr/bin/prename, which is a perl script
See rename --help before your move.

List files not matching a pattern?

Here's how one might list all files matching a pattern in bash:
ls *.jar
How to list the complement of a pattern? i.e. all files not matching *.jar?
Use egrep-style extended pattern matching.
ls !(*.jar)
This is available starting with bash-2.02-alpha1.
Must first be enabled with
shopt -s extglob
As of bash-4.1-alpha there is a config option to enable this by default.
ls | grep -v '\.jar$'
for instance.
Little known bash expansion rule:
ls !(*.jar)
With an appropriate version of find, you could do something like this, but it's a little overkill:
find . -maxdepth 1 ! -name '*.jar'
find finds files. The . argument specifies you want to start searching from ., i.e. the current directory. -maxdepth 1 tells it you only want to search one level deep, i.e. the current directory. ! -name '*.jar' looks for all files that don't match the regex *.jar.
Like I said, it's a little overkill for this application, but if you remove the -maxdepth 1, you can then recursively search for all non-jar files or what have you easily.
POSIX defines non-matching bracket expressions, so we can let the shell expand the file names for us.
ls *[!j][!a][!r]
This has some quirks though, but at least it is compatible with about any unix shell.
If your ls supports it (man ls) use the --hide=<PATTERN> option. In your case:
$> ls --hide=*.jar
No need to parse the output of ls (because it's very bad) and it scales to not showing multiple types of files. At some point I needed to see what non-source, non-object, non-libtool generated files were in a (cluttered) directory:
$> ls src --hide=*.{lo,c,h,o}
Worked like a charm.
Another approach can be using ls -I flag (Ignore-pattern).
ls -I '*.jar'
And if you want to exclude more than one file extension, separate them with a pipe |, like ls test/!(*.jar|*.bar). Let's try it:
$ mkdir test
$ touch test/1.jar test/1.bar test/1.foo
$ ls test/!(*.jar|*.bar)
test/1.foo
Looking at the other answers you might need to shopt -s extglob first.
One solution would be ls -1|grep -v '\.jar$'
Some mentioned variants of this form:
ls -d *.[!j][!a][!r]
But this seems to be only working on bash, while this seems to work on both bash and zsh:
ls -d *.[^j][^a][^r]
ls -I "*.jar"
-I, --ignore=PATTERN
do not list implied entries matching shell PATTERN
It works without having to execute anything before
It works also inside watch quotes: watch -d 'ls -I "*.gz"', unlike watch 'ls !(*.jar)' which produces: sh: 1: Syntax error: "(" unexpected
Note: For some reason in Centos requires quoting the pattern after -I while Ubuntu does not

Resources