I implemented the following command to create a file called * (asterisk):
echo > '*'
Now I'm supposed to remove this file without using any quotations.
I know how to remove this by using quotations, but not sure how without using quotations.
I tried the following commands which I was sure that they won't work because of command line expansion:
rm ./*
rm /*
If someone can help me with this, I would greatly appreciate it.
I think you're supposed to work this out yourself :-)
The simplest solution not involving quoting is to use the pattern [*]. Bracket expressions in a shell work much like character classes in regular expressions so that will match a file whose name is the single character *. Thus, you can delete your file with
rm [*]
Note that you cannot use that pattern to create a file named * because the shell substitutes words containing patterns with the name(s) of the files which match the pattern; if no such file exists, then the pattern is not matched and no substitution is performed. So if there is no file named *, then touch [*] will create a file named [*].
You could use history expansion. If the rm command directly follows the echo command, you can use !$:
echo > '*'
rm !$
!$ is shorthand for !!:$: repeat the last word ($) of the last command (!!).
If there are commands between the echo and the rm command, you can find the history number using fc -l:
$ echo > '*'
$ cmd1
$ cmd2
$ cmd3
$ fc -l
[...]
27628 echo > '*'
27629 cmd1
27630 cmd2
27631 cmd3
$ rm !27628:$
!27628 expands to the command with that number in the history, and $ is again the last word of that command.
If you have to run this in a script, you can't really look up the command number and insert it, but you can count the number of commands between the echo and the rm and use a relative event designator:
echo > '*'
cmd1
rm !-2:$
where !-2 refers to the command two lines back. Notice that history expansion is by default disabled in non-interactive shells; use
shopt -o history
to enable it.
You could use rm -i * if the number of files is not too big. This will ask for confirmation for every single file. Confirm deletion only for the file * and reject it for all others.
Related
I have a number of files with the following naming:
name1.name2.s01.ep01.RANDOMWORD.mp4
name1.name2.s01.ep02.RANDOMWORD.mp4
name1.name2.s01.ep03.RANDOMWORD.mp4
I need to remove everything between the last . and ep# from the file names and only have name1.name2.s01.ep01.mp4 (sometimes the extension can be different)
name1.name2.s01.ep01.mp4
name1.name2.s01.ep02.mp4
name1.name2.s01.ep03.mp4
This is a simpler version of #Jesse's [answer]
for file in /path/to/base_folder/* #Globbing to get the files
do
epno=${file#*.ep}
mv "$file" "${file%.ep*}."ep${epno%%.*}".${file##*.}"
#For the renaming part,see the note below
done
Note : Didn't get a grab of shell parameter expansion yet ? Check [ this ].
Using Linux string manipulation (refer: http://www.tldp.org/LDP/abs/html/string-manipulation.html) you could achieve like so:
You need to do per file-extension type.
for file in <directory>/*
do
name=${file}
firstchar="${name:0:1}"
extension=${name##${firstchar}*.}
lastchar=$(echo ${name} | tail -c 2)
strip1=${name%.*$lastchar}
lastchar=$(echo ${strip1} | tail -c 2)
strip2=${strip1%.*$lastchar}
mv $name "${strip2}.${extension}"
done
You can use rename (you may need to install it). But it works like sed on filenames.
As an example
$ for i in `seq 3`; do touch "name1.name2.s01.ep0$i.RANDOMWORD.txt"; done
$ ls -l
name1.name2.s01.ep01.RANDOMWORD.txt
name1.name2.s01.ep02.RANDOMWORD.txt
name1.name2.s01.ep03.RANDOMWORD.txt
$ rename 's/(name1.name2.s01.ep\d{2})\..*(.txt)$/$1$2/' name1.name2.s01.ep0*
$ ls -l
name1.name2.s01.ep01.txt
name1.name2.s01.ep02.txt
name1.name2.s01.ep03.txt
Where this expression matches your filenames, and using two capture groups so that the $1$2 in the replacement operation are the parts outside the "RANDOMWORD"
(name1.name2.s01.ep\d{2})\..*(.txt)$
Hi… Need a little help here…
I tried to emulate the DOS' dir command in Linux using bash script. Basically it's just a wrapped ls command with some parameters plus summary info. Here's the script:
#!/bin/bash
# default to current folder
if [ -z "$1" ]; then var=.;
else var="$1"; fi
# check file existence
if [ -a "$var" ]; then
# list contents with color, folder first
CMD="ls -lgG $var --color --group-directories-first"; $CMD;
# sum all files size
size=$(ls -lgGp "$var" | grep -v / | awk '{ sum += $3 }; END { print sum }')
if [ "$size" == "" ]; then size="0"; fi
# create summary
if [ -d "$var" ]; then
folder=$(find $var/* -maxdepth 0 -type d | wc -l)
file=$(find $var/* -maxdepth 0 -type f | wc -l)
echo "Found: $folder folders "
echo " $file files $size bytes"
fi
# error message
else
echo "dir: Error \"$var\": No such file or directory"
fi
The problem is when the argument contains an asterisk (*), the ls within the script acts differently compare to the direct ls command given at the prompt. Instead of return the whole files list, the script only returns the first file. See the video below to see the comparation in action. I don't know why it behaves like that.
Anyone knows how to fix it? Thank you.
Video: problem in action
UPDATE:
The problem has been solved. Thank you all for the answers. Now my script works as expected. See the video here: http://i.giphy.com/3o8dp1YLz4fIyCbOAU.gif
The asterisk * is expanded by the shell when it parses the command line. In other words, your script doesn't get a parameter containing an asterisk, it gets a list of files as arguments. Your script only works with $1, the first argument. It should work with "$#" instead.
This is because when you retrieve $1 you assume the shell does NOT expand *.
In fact, when * (or other glob) matches, it is expanded, and broken into segments by $IFS, and then passed as $1, $2, etc.
You're lucky if you simply retrieved the first file. When your first file's path contains spaces, you'll get an error because you only get the first segment before the space.
Seriously, read this and especially this. Really.
And please don't do things like
CMD=whatever you get from user input; $CMD;
You are begging for trouble. Don't execute arbitrary string from the user.
Both above answers already answered your question. So, i'm going a bit more verbose.
In your terminal is running the bash interpreter (probably). This is the program which parses your input line(s) and doing "things" based on your input.
When you enter some line the bash start doing the following workflow:
parsing and lexical analysis
expansion
brace expansion
tidle expansion
variable expansion
artithmetic and other substitutions
command substitution
word splitting
filename generation (globbing)
removing quotes
Only after all above the bash
will execute some external commands, like ls or dir.sh... etc.,
or will do so some "internal" actions for the known keywords and builtins like echo, for, if etc...
As you can see, the second last is the filename generation (globbing). So, in your case - if the test* matches some files, your bash expands the willcard characters (aka does the globbing).
So,
when you enter dir.sh test*,
and the test* matches some files
the bash does the expansion first
and after will execute the command dir.sh with already expanded filenames
e.g. the script get executed (in your case) as: dir.sh test.pas test.swift
BTW, it acts exactly with the same way for your ls example:
the bash expands the ls test* to ls test.pas test.swift
then executes the ls with the above two arguments
and the ls will print the result for the got two arguments.
with other words, the ls don't even see the test* argument - if it is possible - the bash expands the wilcard characters. (* and ?).
Now back to your script: add after the shebang the following line:
echo "the $0 got this arguments: $#"
and you will immediatelly see, the real argumemts how your script got executed.
also, in such cases is a good practice trying to execute the script in debug-mode, e.g.
bash -x dir.sh test*
and you will see, what the script does exactly.
Also, you can do the same for your current interpreter, e.g. just enter into the terminal
set -x
and try run the dir.sh test* = and you will see, how the bash will execute the dir.sh command. (to stop the debug mode, just enter set +x)
Everbody is giving you valuable advice which you should definitely should follow!
But here is the real answer to your question.
To pass unexpanded arguments to any executable you need to single quote them:
./your_script '*'
The best solution I have is to use the eval command, in this way:
#!/bin/bash
cmd="some command \"with_quetes_and_asterisk_in_it*\""
echo "$cmd"
eval $cmd
The eval command takes its arguments and evaluates them into the command as the shell does.
This solves my problem when I need to call a command with asterisk '*' in it from a script.
I often find myself using mv to rename a file. E.g.
mv app/models/keywords_builder.rb app/models/keywords_generator.rb
Doing so I need to write (ok, tab complete) the path for the second parameter. In this example it isn't too bad but sometimes the path is deeply nested and it seems like quite a bit of extra typing.
Is there a more efficient way to do this?
And another way: brace expansion.
mv app/models/keywords_{builder,generator}.rb
In general,
before{FIRST,SECOND}after
expands to
beforeFIRSTafter beforeSECONDafter
So it's also useful for other renames, e.g.
mv somefile{,.bak}
expands to
mv somefile somefile.bak
It works in bash and zsh.
More examples:
Eric Bergen > Bash Brace Expansion
Bash Brace Expansion | Linux Journal
You can use history expansion like this:
mv app/modules/keywords_builder.rb !#^:h/keywords_generator.rb
! introduces history expansion.
# refers to the command currently being typed
^ means the first argument
:h is a modifier to get the "head", i.e. the directory without the file part
It's supported in bash and zsh.
Docs:
bash history expansion
zsh history expansion
One way is to type the first file name and a space, then press Ctrl+w to delete it. Then press Ctrl+y twice to get two copies of the file name. Then edit the second copy.
For example,
mv app/models/keywords_builder.rb <Ctrl+W><Ctrl+Y><Ctrl+Y><edit as needed>
or cd apps/models && mv keywords_builder.rb keywords_generator.rb && cd -
Combined answers of Mikel and geekosaur with additonal use of ":p"
use brace expansion to avoid first argument repetition:
mv -iv {,old_}readme.txt # 'readme.txt' -> 'old_readme.txt'
mv -iv file{,.backup} # 'file' -> 'file.backup'
use history expansion to avoid first argument repetition:
mv -iv "system file" !#$.backup # 'system file' -> 'system file.backup'
the filename can be printed using the "p" designator for further edition:
mv -iv "file with a long name" !#$:p
then press "↑" to edit the command
I want to copy a bunch of Verilog/systemverilog sources, so I use CP with wildcard expression:
cp <some_dir>/*.{v,sv,svh} .
It works. But when I put it to a script with exactly the same line, the CP command fails with the log:
cp: cannot stat `../../mytest/spiTest/*.{v,sv,svh}': No such file or directory
How is that happening?
PS: I use bash as the shell.
And here is my script:
#!/bin/bash
rdir=../../mytest/spiTest
f1="$rdir/bench.lst"
f2="$rdir/cphex" #the script to copy rom data
f3="$rdir/make*" #makefile scripts
f4="$rdir/*.hex" #rom files
f5="$rdir/*.{v,sv,svh}" #testbench files
echo 'Copying files...'
cp $f1 $f2 $f3 $f4 .
cp $f5 .
I do changed the first line to
#!/bin/bash -vx
and run this script again, and I get:
#!/bin/bash -vx
rdir=../../mytest/spiTest
+ rdir=../../mytest/spiTest
f1="$rdir/bench.lst"
+ f1=../../mytest/spiTest/bench.lst
f2="$rdir/cphex" #the script to copy rom data
+ f2=../../mytest/spiTest/cphex
f3="$rdir/make*" #makefile scripts
+ f3='../../mytest/spiTest/make*'
f4="$rdir/*.hex" #rom files
+ f4='../../mytest/spiTest/*.hex'
f5="$rdir/*.{v,sv,svh}" #testbench files
+ f5='../../mytest/spiTest/*.{v,sv,svh}'
echo 'Copying files...'
+ echo 'Copying files...'
Copying files...
cp $f1 $f2 $f3 $f4 .
+ cp ../../mytest/spiTest/bench.lst ../../mytest/spiTest/cphex ../../mytest/spiTest/makefile ../../mytest/spiTest/makefile.defines ../../mytest/spiTest/rom.hex ../../mytest/spiTest/rom_if.hex .
cp $f5 .
+ cp '../../mytest/spiTest/*.{v,sv,svh}' .
cp: cannot stat `../../mytest/spiTest/*.{v,sv,svh}': No such file or directory
Check the first line of the script. It probably reads:
#!/bin/sh
which switches the shell from BASH to Bourne Shell. Use
#!/bin/bash
instead.
[EDIT] You're running into problems with expansion. BASH has a certain order in which it expands patterns and variables. That means:
f5="$rdir/*.{v,sv,svh}" #testbench files
is quoted, so no file name expansion happens at this time. Only the variable $rdir is expanded. When
cp $f5 .
is executed, BASH first looks for file names to expand and there are none. Then it expands variables (f5) and then calls cp with two arguments: ../../mytest/spiTest/*.{v,sv,svh} and .. Since cp expects the shell to have performed the file name expansion already, you get an error.
To fix this, you have to use arrays:
f5=($rdir/*.{v,sv,svh})
This replaces the variable and then expands the file names and puts everything into the array f5. You can then call cp with this array while preserving whitespaces:
cp "${f5[#]}" .
Every single character here is important. [#] tells BASH to expand the whole array here. The quotes say: Preserve whitespace. {} is necessary to tell BASH that [#] is part of the variable "name" to expand.
Here's the problem: the order of substitutions. Bash performs brace expansion before variable expansion. In the line cp $f5 ., bash will do:
brace expansion: n/a
this is the key point: the variable contains a brace expression, but the shell does not see it now when it needs to.
tilde expansion: n/a
parameter expansion: yes -- cp ../../mytest/spiTest/*.{v,sv,svh} .
command substitution: n/a
arithmetic expansion: n/a
process substitution: n/a
word splitting: n/a
filename expansion: yes, bash looks for files in that directory ending with the string
.{v,sv,svh}. It finds none, nullglob is not set, thus the pattern is not removed from the command
quote removal: n/a
Now the command is executed and fails with the error you see.
https://www.gnu.org/software/bash/manual/bashref.html#Shell-Expansions
Solutions:
use Aaron's idea of an array
(not recommended) force a 2nd round of expansions: eval cp $f5 .
The line
f5="$rdir/*.{v,sv,svh}" #testbench files
is probably wrong. First, avoid comments at end of line, they should be (at least for readability) in a separate line. Then, avoid using globbing in variable assignment. So remove that line, and code later (that is, replace the old cp $f5 . line with)
cp "$rdir"/*.{v,sv,svh} .
BTW, I would test that "$rdir" is indeed a directory with
if [ ! -d "$rdir" ] ; then
echo invalid directory $rdir > /dev/stderr
exit 1
fi
You should read the Advanced Bash Scripting Guide
It has been a long time since I did much bash script writing.
This is a bash script to copy and rename files by deleting all before the first period delimiter:
#!/bin/bash
mkdir fullname
mv *.audio fullname
cd fullname
for x in * ;
do
cp $x ../`echo $x | cut -d "." -f 2-`
done
cd ..
ls
It works well for file names with no embedded spaces but not for those with spaces.
How can I change the code to fix this simple Linux bash script? Any suggestions for improving the code for other reasons would also be welcome.
Example filenames, some with embedded spaces and some not (from link)
http://www.homenetvideo.com/demo/index.php?/Radio%20%28VLC%29
Ambient.A6.SOMA Space Station.audio
Blues.B9.Blues Radio U.K.audio
Classical.K3.Radio Stephansdom - Vienna.audio
College.CI.KDVS U of California, Davis.audio
Country.Q1.K-FROG.audio
Easy.G4.WNYU.audio
Eclectic.M2.XPN.audio
Electronica.E2.Rinse.audio
Folk.F1.Radionomy.audio
Hiphop.H1.NPR.audio
Indie.I4.WAUG.audio
Jazz.J6.KCSM.audio
Latin.L3.Mega.audio
Misc.X7.Gaydio.audio
News.N9.KQED.audio
Oldies.O1.Lonestar.audio
OldTime.Y1.Roswell.audio
Progressive.P1.Aural Moon.audio
Rock.R8.WXRT.audio
Scanner.Z3.Montreal.audio
Soul.S1.181.FM.audio
Talk.T2.TWiT.audio
World.W3.Persian.audio
http://lh5.googleusercontent.com/-QjLEiAtT4cw/U98_UFcWvvI/AAAAAAAABv8/gyPhbg8s7Bw/w681-h373-no/homenet-radio.png
Whenever you deal with file names that might have spaces in them, you must reference them as "$x" rather than just $x. That's what's causing your cp command to fail.
Your echo command is also problematic. Although echo does the right thing for simple spaces - it echoes a file named A B C as A B C - it will still fail if you have more than one consecutive space in the name, or whitespace that isn't a simple space character.
Instead of passing the file names to external programs for processing, which always requires getting them through the whitespace-hostile command line, you should use bash built-in functions for string manipulations wherever possible, e.g. ${x%%foo}, ${x#bar} and similar functions. The man page describes them under "Parameter expansion".
Here's my suggestion:
#!/bin/bash
shopt -s nullglob
mkdir fullname
mv *.audio fullname
(
cd fullname || exit
for x in *; do
cp "$x" "../${x#*.}"
done
)
ls
nullglob prevents * from presenting itself if no file matches it. Just optional.
() summons a subshell and saves you from changing back to another directory.
|| exit terminates the subshell if cd fails to change directory.
${x#*.} removes the <first>. from $x and expands it.