so the thing is, I'm trying to code a shell script that creates multiple image files in an ascending order, but, the ones with only one digit must come with a zero first. I'm a beginner and searching throughout the internet I've found that there are some syntax differences when it comes to the indexing system, so on my computer the following "for" syntax works fine, but I'm sure there's a better approach to that. there it goes:
for i in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
do
if (${i:0:1} == "0") then
touch "0${i}.jpg"
else
touch "$i.jpg"
fi
done
it returns "bad substitution". is there anything wrong with this approach?
for i in {0..15}; do printf -v name "%02i.jpg" $i; touch "$name" ; done
Or, if it is easier to read:
for i in {0..15}; do
printf -v name "%02i.jpg" $i
touch "$name"
done
When this is done, the following files are created:
$ ls
00.jpg 01.jpg 02.jpg 03.jpg 04.jpg 05.jpg 06.jpg 07.jpg 08.jpg 09.jpg 10.jpg 11.jpg 12.jpg 13.jpg 14.jpg 15.jpg
Explanation
for i in {0..15}; do
This starts the loop.
printf -v name "%02i.jpg" $i
This creates a shell variable name which has the desired format. %02i instructs printf to write the number out in two space with zero-padding.
touch "$name"
This creates the file
done
This signals the end of the loop.
Alternative
Starting from the script in the question, a couple changes are needed on the if statement. The following works:
for i in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
do
if [ "$i" -le 9 ]; then
touch "0${i}.jpg"
else
touch "$i.jpg"
fi
done
Related
This problem is a homework, I know there would be much easier ways of solving this problem, but it is what it is.
The question goes as follows:
-I have a bash script that does some creates some files.
-I have to pass this first argument of this script ( which is a directory ) to another script trough a pipeline ( | ).
The problem comes in when I do try and pass this directory as an argument, in the other script, the argument I receive is null.
This is the whole code, in this exact order.
Nothing was left out.
The first script receives at least 11 argument:
if [[ $# -lt 11 ]]; then
echo "Not enough arguments." >&2;
exit 1;
fi;
The script will check if the first argument is an actual directory:
if [[ ! -d $1 ]]; then
echo "Not a valid first argument." >&2;
exit 1;
fi;
Here, I'll save my first directory here so that I can shift, and then I'm going to declare more stuff that i need
Directory=$1;
shift;
N=$#
name_array=("$#")
Pos=0;
Afterwards, the script will use all the other arguments to create .txt files with those names, and add a number of non-null lines in each .txt file ( So, argument2.txt has N-1 lines, argument3.txt has N-2 lines ... argumentN.txt has 1 line.) Afterwards, I have to change their permissions to 600.
while [[ $# -gt 0 ]]; do
if [[ -a "$Directory"/"$1".txt ]]; then
rm "$Directory"/"$1".txt;
fi
touch "$Directory"/"$1".txt
for((i=0;i<N;i++)); do
echo "$N" >> "$Directory"/"$1".txt;
done;
chmod u=rw "$Directory"/"$1".txt;
N=$(($N-1));
name_array["$Pos"]="$Directory"/"$1".txt;
Pos=$(($Pos + 1));
shift;
done;
Problem comes here, I'm trying to echo the first directory..
echo $Directory;
I have tried this as well, with the same results as the one above
echo "$(cd "$(dirname "$Directory")" && pwd -P)/$(basename "$Directory")";
.. like this so I can use in the Shell the pipeline commands.
The second script will receive the directory, enter it and search trough all the files that have been created just now.
In the Unix terminal, I use this:
./FirstScript.bash 1 2 3 4 5 6 7 8 9 10 11 12 | ./SecondScript.bash
Thank you in advance!
Thanx for being honest, and of course we're not going to do your homework, but,...
Most of the code seems pretty good. There are some smaller issues, which you might easily solve with shellcheck (if it isn't installed, install it or use the web version), and I would not put ; at the end of each line.
So, instead of running the complete pipeline at once, break it down to find where your problem lies. If you run
./FirstScript.bash 1 2 3 4 5 6 7 8 9 10 11 12
If the output is as expected (so, with the echo $Directory), and the files are created, then the first script is good. So, in this case, the directory 1 will contain the files 2 3 4 5 6 7 8 9 10 11 12, and the output will be 1.
Then, you will run the second script
./SecondScript.bash
and as input you will give:
1
^D
(that is control-d on Unix). And then the second script should do what you want (or not).
In this way you can debug the scripts separately.
(quick face-palm question: is 1 really the directory you want, or did you just forget the directory name as first argument?)
I am trying to script a procedure but I am really stuck now.
In a folder, I have some files, let say:
000001.dat
000002.dat
000003.dat
000004.dat
000005.dat
000006.dat
000007.dat
000008.dat
000009.dat
000010.dat
In a variable, I have echo $num > 000009.
What I would like to do is to suppress the intermediate file like:
rm -f {000001..${num}}*, but it doesn't work...
If I use rm -f {000001..000009}*, it works! So I think it is a problem when reading the num variable.
Any helps? :)
Thank you in advance!
One way using eval:
$ x=1;y=9
$ echo {$x..$y}
{1..9}
$ eval echo {$x..$y}
1 2 3 4 5 6 7 8 9
So, I am building a bash script which iterates through folders named by numbers from 1 to 9. The script depends on getting the folder names by user input. My intention is to use a for loop using read input to get a folder name or a range of folder names and then do some stuff.
Example:
Let's assume I want to make a backup with rsync -a of a certain range of folders. Usually I would do:
for p in {1..7}; do
rsync -a $p/* backup.$p
done
The above would recursively backup all content in the directories 1 2 3 4 5 6 and 7 and put them into folders named as 'backup.{index-number}'. It wouldn't catch folders/files with a leading . but that is not important right now.
Now I have a similar loop in an interactive bash script. I am using select and case statements for this task. One of the options in case is this loop and it shall somehow get a range of numbers from user input. This now becomes a problem.
Problem:
If I use read to get the range then it fails when using {1..7} as input. The input is taken literally and the output is just:
{1..7}
I really would like to know why this happens. Let me use a more descriptive example with a simple echo command.
var={1..7} # fails and just outputs {1..7}
for p in $var; do echo $p;done
read var # Same result as above. Just outputs {1..7}
for p in $var; do echo $p;done
for p in {1..7}; do echo $p;done # works fine and outputs the numbers 1-7 seperated with a newline.
I've found a workaround by storing the numbers in an array. The user can then input folder names seperated by a space character like this: 1 2 3 4 5 6 7
read -a var # In this case the output is similar to the 3rd loop above
for p in ${var[#]}; do echo $p; done
This could be a way to go but when backing up 40 folders ranging from 1-40 then adding all the numbers one-by-one completely makes my script redundant. One could find a solution to one of the millennium problems in the same time.
Is there any way to read a range of numbers like {1..9} or could there be another way to get input from terminal into the script so I can iterate through the range within a for-loop?
This sounds like a question for google but I am obviously using the wrong patterns to get a useful answer. Most of similar looking issues on SO refer to brace and parameter expansion issues but this is not exactly the problem I have. However, to me it feels like the answer to this problem is going in a similar direction. I fail to understand why when a for-loop for assigning {1..7} to a variable works but doing the same like var={1..7} doesn't. Plz help -.-
EDIT: My bash version:
$ echo $BASH_VERSION
4.2.25(1)-release
EDIT2: The versatility of a brace expansion is very important to me. A possible solution should include the ability to define as many ranges as possible. Like I would like to be able to choose between backing up just 1 folder or a fixed range between f.ex 4-22 and even multiple options like folders 1,2,5,6-7
Brace expansion is not performed on the right-hand side of a variable, or on parameter expansion. Use a C-style for loop, with the user inputing the upper end of the range if necessary.
read upper
for ((i=1; i<=$upper; i++)); do
To input both a lower and upper bound separated by whitespace
read lower upper
for (i=$lower; i <= $upper; i++)); do
For an arbitrary set of values, just push the burden to the user to generate the appropriate list; don't try to implement your own parser to process something like 1,2,20-22:
while read p; do
rsync -a $p/* backup.$p
done
The input is one value per line, such as
1
2
20
21
22
Even if the user is using the shell, they can call your script with something like
printf '%s\n' 1 2 20..22 | backup.sh
It's easier for the user to generate the list than it is for you to safely parse a string describing the list.
The evil eval
$ var={1..7}
$ for i in $(eval echo $var); do echo $i; done
this also works,
$ var="1 2 {5..9}"
$ for i in $(eval echo $var); do echo $i; done
1
2
5
6
7
8
9
evil eval was a joke, that is, as long as you know what you're evaluating.
Or, with awk
$ echo "1 2 5-9 22-25" |
awk -v RS=' ' '/-/{split($0,a,"-"); while(a[1]<=a[2]) print a[1]++; next}1'
1
2
5
6
7
8
9
22
23
24
25
Hello i am new to shell script. want to execute a binary through loop in a shell script.
wrote a pgm which looked like:
i="1"
while [ $i -lt 100 ]
do
/home/rajni/BUFFER_SEND_STUB/build/buffer_send.exe
i=`expr $i +1`
done
doubt it is not working fine. Can anyone suggest????
Thanks.
expr won't like the fact you've used +1 rather than the space-separated + 1.
I also tend to use [[ and ]] rather than the single ones since they're definitely bash-internal and more powerful than the external [/test.
In any case, there's a more efficient way if you're using a relatively recent bash:
for i in {1..100} ; do
echo $i
done
which will do something with each value 1 through 100 inclusive (your current loop does 1 through 99 so you may have to adjust for that).
Changing that 100 to a 5 shows how it works, generating:
1
2
3
4
5
you can use the for loop
for i in {1..100}
do
/home/rajni/BUFFER_SEND_STUB/build/buffer_send.exe
done
I'd like to change the file name suffix from files (using a bash script), but sometimes there are files with one period and some with two.
Now I use this:
new_file=`echo ${file} | sed 's/\(.*\.log.*\)'${suf}'/\1.'${num}'/'`
Where 'new_file' is the new file name, 'file' the original file name, '${suf}' the file's suffix and ${num} a new number.
So some.log must become some.log.1 and some.log.1 must become some.log.2. With my code some.log becomes some.log.1, but some.log.1 remains some.log.1.
I hope I'm clear enough. I appreciate any advice (even not using sed).
Update:
#paxdiablo. Something went wrong testing I think.
Now I use this piece of code as test;
#!/usr/bin/bash
shft() {
for suff in {6..1} ; do
if [[ -f "$1.${suff}" ]] ; then
((nxt = suff + 1))
echo Moving "$1.${suff}" to "$1.${nxt}"
mv -f "$1.${suff}" "$1.${nxt}"
fi
done
echo Moving "$1" to "$1.1"
mv -f "$1" "$1.1"
}
clear
folder=~/logs/*.log
for i in {1..20}; do
echo ${i}> ~/logs/some.log
for fspec in ${folder} ; do
shft "${fspec}"
done
done
Every thing works fine now. Sorry for the confusion.
If you're looking to roll over log files, and depending on how complex you need to get, I've used the following segment before:
#!/usr/bin/bash
# rollover.sh
# Rolls over log files in the current directory.
# *.log.8 -> *.log.9
# *.log.7 -> *.log.8
# : : :
# *.log.1 -> *.log.2
# *.log -> *.log.1
shft() {
# Change this '8' to one less than your desired maximum rollover file.
# Must be in reverse order for renames to work (n..1, not 1..n).
for suff in {8..1} ; do
if [[ -f "$1.${suff}" ]] ; then
((nxt = suff + 1))
echo Moving "$1.${suff}" to "$1.${nxt}"
mv -f "$1.${suff}" "$1.${nxt}"
fi
done
echo Moving "$1" to "$1.1"
mv -f "$1" "$1.1"
}
for fspec in *.log ; do
shft "${fspec}"
#date >"${fspec}" #DEBUG code
done
This will automatically roll over log files up to version 9 although you can just change the suff for loop to allow more.
With that DEBUG added so new files are created automatically for testing, the following transcript shows it in action:
pax> touch qq.log ; ./rollover.sh
Moving "qq.log" to "qq.log.1"
pax> touch "has spaces.log" ; ./rollover.sh
Moving "has spaces.log" to "has spaces.log.1"
Moving "qq.log.1" to "qq.log.2"
Moving "qq.log" to "qq.log.1"
pax> ll *log*
-rw-r--r-- 1 pax None 30 2010-09-11 20:39 has spaces.log
-rw-r--r-- 1 pax None 0 2010-09-11 20:39 has spaces.log.1
-rw-r--r-- 1 pax None 30 2010-09-11 20:39 qq.log
-rw-r--r-- 1 pax None 30 2010-09-11 20:38 qq.log.1
-rw-r--r-- 1 pax None 0 2010-09-11 20:38 qq.log.2
The good thing about this script is that it's easily configurable to handle a large amount of history (by changing the {8..1} bit), handles names with spaces, and handles gaps relatively robustly if log files go missing.
To rotate logs, you should really use logrotate.
If you can't rely on logrotate being available, here's a way do it inside the shell. To keep things simple, I'll assume that nothing else (including another instance of your script) will try to rename the log files while your script is running.
The easiest approach is to recursively rename log N+1 before actually renaming log N to N+1. The shell can perform all the necessary arithmetic, you don't need sed here. Note that while recursive functions are possible in a POSIX shell, there are no local variables other than the positional parameters (many shells offer local variables as an extension).
#!/bin/sh
## Move "$1.$2" to "$1.$(($2+1))", first rotating the target as well.
rotate () {
if [ -e "$1.$(($2+1))" ]; then rotate "$1" $(($2+1)); fi
mv -- "$1.$2" "$1.$(($2+1))"
}
for x; do
## Break each argument into FILE.NUMBER or just FILE.
suffix=${x##*.}
case $suffix in
*[!0-9]*)
if [ -e "$x.0" ]; then rotate "$x" 0; fi
mv -- "$x" "$x.0";;
*) rotate "${x%.*}" "$suffix";;
esac
done
Regarding what you've written, note that echo ${file} is bad for two reasons: most importantly, if ${file} contains any special characters such as white space, the shell will interpret them; also, with some shells, echo itself will interpret backslashes and possibly a leading -. So you should always write printf %s "$file" instead.