I have a script in which I am attempting to match strings in filenames on either side of a word. The keywords which are meant for pattern matchin with the wildcard character, as in:
ls *spin*.txt
This will of course match any of the following filenames:
one_spin.txt
4_5342spin-yyy.txt
fifty_spinout.txt
...etc.
What I would like to do is use the word 'spin' and a number of other words as match cases in an array that I can pass through a loop. I'd like these matches to be case-insensitive I attempt this like so:
types=(spin wheel rotation)
for file in $types; do
ls '*'${file}'*.txt'
done
EDIT: Because I'm looking for a solution that is maleable, I'd also like to be able to do something like:
types=(spin wheel rotation)
for file in $types; do
find . -iname "*$file*.txt"
done
I'm not sure how bash interprets either of these except seeing that it does not list the desired files. Can someone clarify what is happening in my script, and offer a solution that meets the aforementioned criteria?
Your attempt will work with a little more tweaks. As you are assigning types
as an array, you need to access it as an array.
Would you please try:
types=(spin wheel rotation)
for file in "${types[#]}"; do
ls *${file}*.txt
done
If your bash supports shopt builtin, you can also say:
shopt -s extglob
ls *#(spin|wheel|rotation)*.txt
If you want to make it match in a case-insensitive way, please try:
shopt -s extglob nocaseglob
ls *#(spin|wheel|rotation)*.txt
which will match one_Spin.txt, fifty_SPINOUT.TXT, etc.
Hope this helps.
don't make it complicate please try this instead
ls *{spin,wheel,rotation}*.txt
This also helpfull in creating files
touch 1_{spin,wheel,rotation,ads,sad,zxc}_2.txt
Or dirs
mkdir -p {test,test2/log,test3}
Related
I'm trying to automate the selection of a usb interface on macOS for avrdude. I want to select the first output of ls /dev/tty.usb*.
I'm trying to automate this.
However, I can't seem to get cut to work, and other solutions to similar problems are unfortunately too complex for me abstract to my problem. It would seem something like awk or sed is the correct approach, but I am not familiar with either of these.
For example, I want to get /dev/tty.usbmodem002021332 from running
$ ls /dev/tty.usb*
/dev/tty.usbmodem002021332 /dev/tty.usbmodem002021334 /dev/tty.usbserial-DAYO5CGB
Your struggles with the output ls are an example of why it's never a good idea to rely on ls within a shell script. In order to fetch the list of files that match the pattern, /dev/tty.usb*, an alternative is to assign the result of a glob expression to an array:
#!/bin/bash
shopt -s nullglob
list=(/dev/tty.usb*)
...and then fetch its first element:
echo "${list[0]}"
For more on why ls is problematic, see https://mywiki.wooledge.org/ParsingLs
I have a question concerning why this doesn't work. Probably, it's a simple answer, but I just can't seem to figure it out.
I want to move a couple of files I have. They all have the same filename (let's say file1) but they are all in different directories (lets say /tmp/dir1,dir2 and dir3). If I were to move these individually I could do something along the lines of:
mv /tmp/dir1/file1 /tmp
That works. However, I have multiple directories and they're all going to end up in the same spot....AND I don't want to overwrite. So, I tried something like this:
mv /tmp/{dir1,dir2,dir3}/file1 /tmp/file1.{a,b,c}
When I try this I get:
/tmp/file1.c is not a directory
Just to clarify...this also works:
mv /tmp/dir1/file1 /tmp/file1.c
Pretty sure this has to do with brace expansion but not certain why.
Thanks
Just do echo to understand how the shell expands:
$ echo mv /tmp/{dir1,dir2,dir3}/file1 /tmp/file1.{a,b,c}
mv /tmp/dir1/file1 /tmp/dir2/file1 /tmp/dir3/file1 /tmp/file1.a /tmp/file1.b /tmp/file1.c
Now you can see that your command is not what you want, because in a mv command, the destination (directory or file) is the last argument.
That's unfortunately now how the shell expansion works.
You'll have to probably use an associative array.
!/bin/bash
declare -A MAP=( [dir1]=a [dir2]=b [dir3]=c )
for ext in "${!MAP[#]}"; do
echo mv "/tmp/$ext/file1" "/tmp/file1.${MAP[$ext]}"
done
You get the following output when you run it:
mv /tmp/dir2/file1 /tmp/file1.b
mv /tmp/dir3/file1 /tmp/file1.c
mv /tmp/dir1/file1 /tmp/file1.a
Like with many other languages key ordering is not guaranteed.
${!MAP[#]} returns an array of all the keys, while ${MAP[#]} returns the an array of all the values.
Your syntax of /tmp/{dir1,dir2,dir3}/file1 expands to /tmp/dir1/file /tmp/dir2/file /tmp/dir3/file. This is similar to the way the * expansion works. The shell does not execute your command with each possible combination, it simply executes the command but expands your one value to as many as are required.
Perhaps instead of a/b/c you could differentiate them with the actual number of the dir they came from?
$: for d in 1 2 3
do echo mv /tmp/dir$d/file1 /tmp/file1.$d
done
mv /tmp/dir1/file1 /tmp/file1.1
mv /tmp/dir2/file1 /tmp/file1.2
mv /tmp/dir3/file1 /tmp/file1.3
When happy with it, take out the echo.
A relevant point - brace expansion is not a wildcard. It has nothing to do with what's on disk. It just creates strings.
So, if you create a bunch of files named with single letters or digits, echo ? will wildcard and list them all, but only the ones actually present. If there are files for vowels but not consonants, only the vowels will show. But -
if you say echo {foo,bar,nope} it will output foo bar nope regardless of whether or not any or all of those exist as files or directories, etc.
I am trying to loop through files of a list of specified extensions with a bash script. I tried the solution given at Matching files with various extensions using for loop but it does not work as expected. The solution given was:
for file in "${arg}"/*.{txt,h,py}; do
Here is my version of it:
for f in "${arg}"/*.{epub,mobi,chm,rtf,lit,djvu}
do
echo "$f"
done
When I run this in a directory with an epub file in it, I get:
/*.epub
/*.mobi
/*.chm
/*.rtf
/*.lit
/*.djvu
So I tried changing the for statement:
for f in "${arg}"*.{epub,mobi,chm,rtf,lit,djvu}
Then I got:
089281098X.epub
*.mobi
*.chm
*.rtf
*.lit
*.djvu
I also get the same result with:
for f in *.{epub,mobi,chm,rtf,lit,djvu}
So it seems that the "${arg}" argument is unnecessary.
Although either of these statements finds files of the specified extensions and can pass them to a program, I get read errors from the unresolved *. filenames.
I am running this on OS X Mountain Lion. I was aware that the default bash shell was outdated so I upgraded it from 3.2.48 to 4.2.45 using homebrew to see if this was the problem. That didn't help so I am wondering why I am getting these unexpected results. Is the given solution wrong or is the OS X bash shell somehow different from the *NIX version? Is there perhaps an alternate way to accomplish the same thing that might work better in the OS X bash shell?
This may be a BASH 4.2ism. It does not work in my BASH which is still 3.2. However, if you shopt -s extglob, you can use *(...) instead:
shopt -s extglob
for file in *.*(epub|mobi|chm|rtf|lit|djvu)
do
...
done
#David W.: shopt -s extglob for f in .(epub|mobi|chm|rtf|lit|djvu) results in: 089281098X.epub #kojiro: arg=. shopt -s nullglob for f in "${arg}"/.{epub,mobi,chm,rtf,lit,djvu} results in: ./089281098X.epub shopt -s nullglob for f in "${arg}".{epub,mobi,chm,rtf,lit,djvu} results in: 089281098X.epub So all of these variations work but I don't understand why. Can either of you explain what is going on with each variation and what ${arg} is doing? I would really like to understand this so I can increase my knowledge. Thanks for the help.
In mine:
for f in *.*(epub|mobi|chm|rtf|lit|djvu)
I didn't include ${arg} which expands to the value of $arg. The *(...) matches the pattern found in the parentheses which is one of any of the series of extensions. Thus, it matches *.epub.
Kojiro's:
arg=.
shopt -s nullglob
for f in "${arg}"/*.{epub,mobi,chm,rtf,lit,djvu}
Is including $arg and the slash in his matching. Thus, koriro's start with ./ because that's what they are asking for.
It's like the difference between:
echo *
and
echo ./*
By the way, you could do this with the other expressions too:
echo *.*(epub|mobi|chm|rtf|lit|djvu)
The shell is doing all of the expansion for you. It's really has nothing to do with the for statement itself.
A glob has to expand to an existing, found name, or it is left alone with the asterisk intact. If you have an empty directory, *.foo will expand to *.foo. (Unless you use the nullglob Bash extension.)
The problem with your code is that you start with an arg, $arg, which is apparently empty or undefined. So your glob, ${arg}/*.epub expands to /*.epub because there are no files ending in ".epub" in the root directory. It's never looking in the current directory. For it to do that, you'd need to set arg=. first.
In your second example, the ${arg}*.epub does expand because $arg is empty, but the other files don't exist, so they continue not to expand as globs. As I hinted at before, one easy workaround would be to activate nullglob with shopt -s nullglob. This is bash-specific, but will cause *.foo to expand to an empty string if there is no matching file. For a strict POSIX solution, you would have to filter out unexpanded globs using [ -f "$f" ]. (Then again, if you wanted POSIX, you couldn't use brace expansion either.)
To summarize, the best solutions are to use (most intuitive and elegant):
shopt -s extglob
for f in *.*(epub|mobi|chm|rtf|lit|djvu)
or, in keeping with the original solution given in the referenced thread (which was wrong as stated):
shopt -s nullglob
for f in "${arg}"*.{epub,mobi,chm,rtf,lit,djvu}
This should do it:
for file in $(find ./ -name '*.epub' -o -name '*.mobi' -o -name '*.chm' -o -name '*.rtf' -o -name '*.lit' -o -name '*.djvu'); do
echo $file
done
I am using bash version 4.2.28 on Fedora 16. I have the extglob option set. I am trying to list all files matching *.h or *.cpp using ls *(h|cpp) but the command returns the following:
[agnel#damien cadcore]$ ls *(h|cpp)
ls: cannot access *(h|cpp): No such file or directory
I have verified that there are indeed several .h and .cpp files in my current directory. Am I doing something wrong or could this be a bug in bash or ls?
Update: Thank you for your answers. Using *.h *.cpp does what I need. However, I would still like to know why extglob didn't work like I expected.
The extended glob *(pattern-list) matches 0 or more occurrences of the following pattern list. It does not match an arbitrary string followed by something from the option list. You want:
$ ls *.#(h|cpp)
This matches something, followed by a period, followed by either "h" or "cpp"
I don't think you need complicated globbing in this case: simply try echo *.h *.cpp.
You should be able to do just ls *h *cpp
I have a directory with image files foo_0.jpg to foo_99.jpg. I would like to copy files foo_0.jpg through foo_54.jpg.
Is this possible just using bash wildcards?
I am thinking something like cp foo_[0-54].jpg but I know this selects 0-5 and 4 (right?)
Also, if it is not possible (or efficient) with just wildcards what would be a better way to do this?
Thank you.
I assume you want to copy these files to another directory:
cp -t target_directory foo_{0..54}.jpg
I like glenn jackman answer, but if you really want to use globbing, following might also work for you:
$ shopt -s extglob
$ cp foo_+([0-9]).jpg $targetDir
In extended globbing +() matches one or more instances of whatever expression is in the parentheses.
Now, this will copy ALL files that are named foo_ followed by any number, followed by .jpg. This will include foo_55.jpg, foo_139.jpg, and foo_1223218213123981237987.jpg.
On second thought, glenn jackman has the better answer. But, it did give me a chance to talk about extended globbing.
ls foo_[0-9].jpg foo_[1-4][0-9].jpg foo_5[0-4].jpg
Try it with ls and if that looks good to you then do the copy.
for i in `seq 0 54`; do cp foo_$i.jpg <target>; done
An extension answer of #grok12's answer above
$ ls foo_([0-9]|[0-4][0-9]|5[0-4]).jpg
Basically the regex above will match below
anything with a single digit OR
two digits and that first digit must be between 0-4 and second digit between 0-9 OR
two digits and that first digit is 5 and second digit between 0-9
Alternatively you can achieve similar result with regex below
$ ls file{[0-9],[0-4][0-9],5[0-4]}.txt