I want to generate a series of files in which the file name of each file shall be increased by 1 (File1.txt, File2.txt, File3.txt, ... FileN.txt) where N = 250
Each file has 2 lines.
AAAXXX (where XXX = 001 to 250 - automatic increased for each file)
BBBYYY (where YYY = 3 digit random number )
Example:
File1.txt:
AAA001
BBB175
File5.txt:
AAA005
BBB067
File102.txt:
AAA102
BBB765
I'm a newbie using Ubuntu Linux 12.04 - but I'm hoping someone can assist.
You can do it as follows:
#!/bin/bash
for i in {1..250}
do
printf "AAA%03d\nBBB%03d" ${i} $(($RANDOM % 1000)) > File${i}.txt
done
Explanation:
for i in {1..250} - bash way of specifying iteration from 1 to 250, increment size of 1.
printf - shell printf command - used to print formatted string
AAA - string literal (means "exactly as written")
%03d - formatted string, this prints a decimal number padded with 3 zero's in front.
\n - newline
BBB - another string literal
%03d - same as before
${i} - this is the value used in the first formatted string (%03d)
$(($RANDOM % 1000)) - $RANDOM is a system variable that provides a random number for you each time you access it. The % 1000 to take the modulo so you get a range betwee 0-999. This is used in the 2nd formatted string (%03d)
> File${i}.txt: output redirection; creates and saves to a file (overwrites if file already exists.
Here's a quick one-liner that might start you off:
for i in {1..250}; do printf "AAA%03d\nBBB%03d" $i $(($RANDOM % 1000)) > "File${i}.txt"; done
Using bash:
for i in {1..250}; do printf "AAA%03d\nBBB%03d\n" "$i" "$((RANDOM%1000))" > "File$i.txt"; done
You can write a bash script for this
#!/bin/bash
for (( i=1; i<=250; i++ ))
do
NUMBER=$[ ( $RANDOM % 999 ) + 100 ]
echo "AAA$i BBB$NUMBER" > File$i.txt
done
Related
I'm trying to calculate time difference stored inside of two variables inside of a shell script, I'm observing the following pattern:
hhmm -> 0950
so:
time1=1333
time2=0950
Now I need to calculate the difference in time between time1 and time2, as for now I have tried:
deltaTime=$(($time1-$time2))
but I'm facing the following error message
1333-0950: value too great for base (error token is "0950")
I'm expecting as a result: $deltaTime=0343
Unfortunately, I am strictly bound to use this time pattern. I have already researched for a solution online, some of them propose to use date -d... but I couldn't get it to work :(
Your approach has two issues.
First issue: bash recognizes numbers with leading zeroes as octal. You can force base10 by adding 10# prefix.
Second issue: it is incorrect to consider strings in hhmm format as numbers and substract them. e.g. 1333-950=383 but difference between 09:50 and 13:33 is 3 hours and 43 minutes. You should convert string values to common units, e.g. to minutes, substract them and convert back to hhmm format.
time1=1333
time2=0950
str2min()
{
printf "%u" $((10#${1%??} * 60 + 10#${1#??}))
}
min2str()
{
printf "%02u%02u" $(($1 / 60)) $(($1 % 60))
}
time1m=$(str2min $time1)
time2m=$(str2min $time2)
timediff=$(($time1m - $time2m))
deltaTime=$(min2str $timediff)
You could use this implementation maybe?
#!/usr/bin/env bash
diff_hhmm() {
local -r from=$1
local -i from_hh=10#${from:0:2} # skip 0 chars, read 2 chars (`${from:0:2}`) using base 10 (`10#`)
local -ri from_mm=10#${from:2:2} # skip 2 chars, read 2 chars (`${from:0:2}`) using base 10 (`10#`)
local -r upto=$2
local -ri upto_hh=10#${upto:0:2}
local -ri upto_mm=10#${upto:2:2}
local -i diff_hh
local -i diff_mm
# Compute difference in minutes
(( diff_mm = from_mm - upto_mm ))
# If it's negative, we've "breached" into the previous hour, so adjust
# the `diff_mm` value to be modulo 60 and compensate the `from_hh` var
# to reflect that we've already subtracted some of the minutes there.
if (( diff_mm < 0 )); then
(( diff_mm += 60 ))
(( from_hh -= 1 ))
fi
# Compute difference in hours
(( diff_hh = from_hh - upto_hh ))
# Ensure the result is modulo 24, the number of hours in a day.
if (( diff_hh < 0 )); then
(( diff_hh += 24 ))
fi
# Print the values with 0-padding if necessary.
printf '%02d%02d\n' "$diff_hh" "$diff_mm"
}
$ diff_hhmm 1333 0950
0343
$ diff_hhmm 0733 0950
2143
$ diff_hhmm 0733 0930
2203
Or an even shorter implementation using a big arithmetic compound command ((( ... )) ) and inlining some variables:
diff_hhmm_terse() {
local -i diff_hh diff_mm
((
diff_mm = 10#${1:2:2} - 10#${2:2:2},
diff_hh = 10#${1:0:2} - 10#${2:0:2},
diff_hh -= diff_mm < 0 ? 1 : 0,
diff_mm += diff_mm < 0 ? 60 : 0,
diff_hh += diff_hh < 0 ? 24 : 0
))
printf '%02d%02d\n' "$diff_hh" "$diff_mm"
}
Do you have the possibility to drop the leading zero?
As you can see from my prompt:
Prompt> echo $((1333-0950))
-bash: 1333-0950: value too great for base (error token is "0950")
Prompt> echo $((1333-950))
383
Other proposal:
date '+%s'
Let me give you some examples:
date '+%s'
1662357975
... (after some time)
date '+%s'
1662458180
=>
echo $((1662458180-1662357975))
100205 (amount of seconds)
=>
echo $(((1662458180-1662357975)/3600))
27 (amount of hours)
This bash one-liner may be used if time difference is not negative (that is, time1 >= time2):
printf '%04d\n' $(( 10#$time1 - 10#$time2 - (10#${time1: -2} < 10#${time2: -2} ? 40 : 0) ))
Say I want to search for "ERROR" within a bunch of log files.
I want to print one line for every file that contains "ERROR".
In each line, I want to print the log file path on the left-most edge while the number of "ERROR" on the right-most edge.
I tried using:
printf "%-50s %d" $filePath $errorNumber
...but it's not perfect, since the black console can vary greatly, and the file path sometimes can be quite long.
Just for the pleasure of the eyes, but I am simply incapable of doing so.
Can anyone help me to solve this problem?
Using bash and printf:
printf "%-$(( COLUMNS - ${#errorNumber} ))s%s" \
"$filePath" "$errorNumber"
How it works:
$COLUMNS is the shell's terminal width.
printf does left alignment by putting a - after the %. So printf "%-25s%s\n" foo bar prints "foo", then 22 spaces, then "bar".
bash uses the # as a parameter length variable prefix, so if x=foo, then ${#x} is 3.
Fancy version, suppose the two variables are longer than will fit in one column; if so print them on as many lines as are needed:
printf "%-$(( COLUMNS * ( 1 + ( ${#filePath} + ${#errorNumber} ) / COLUMNS ) \
- ${#errorNumber} ))s%s" "$filePath" "$errorNumber"
Generalized to a function. Syntax is printfLR foo bar, or printfLR < file:
printfLR() { if [ "$1" ] ; then echo "$#" ; else cat ; fi |
while read l r ; do
printf "%-$(( ( 1 + ( ${#l} + ${#r} ) / COLUMNS ) \
* COLUMNS - ${#r} ))s%s" "$l" "$r"
done ; }
Test with:
# command line args
printfLR foo bar
# stdin
fortune | tr -s ' \t' '\n\n' | paste - - | printfLR
I have a string of the form: "8, 14-24, 30-45, 9", which is a substring of the output of pbsnodes. This shows the cores in use on a given node, where 14-24 is a range of cores in use.
I'd like to know the total number of cores in use from this string, i.e.
1 + (24 - 14 + 1) + (45 - 30 + 1 )+ 1 in this example, using a bash script.
Any suggestions or help is much appreciated.
Michael
You could use pure bash techniques to achieve this. By reading the string to array and doing the arithmetic operator using the $((..)) operator. You can run these commands directly on the command-line,
IFS=", " read -ra numArray <<<"8, 14-24, 30-45, 9"
unset count
for word in "${numArray[#]}"; do
(( ${#word} == 1 )) && ((++count)) || count=$(( count + ${word#*-} - ${word%-*} + 1 ))
done
printf "%s\n" "$count"
The idea is
The read with -a splits the string on the de-limiter set by IFS and reads it into the array numArray
A loop on each of the elements, for each element, if the element is just single character, just increment total count by 1
For numeric ranges, do manipulation as e.g. for number a-b use parameter expansion syntax ${word#*-} and ${word%-*} to extract b and a respectively and do b-a+1 and add it with count calculated already and print the element after the loop
You can put this in a bash script with she-bang set to #!/bin/bash and run the script or run it directly from the command-line
I have a piece of script that basically calculates the amount of space the directories in the current directory use but I want help understanding some of the syntax and language etiquette.
Here is the script:
#!/bin/bash
# This script prints a little histogram of how much space
# the directories in the current working directory use
error () {
echo "Error: $1"
exit $2
} >&2
# Create a tempfile (in a BSD- and Linux-friendly way)
my_mktemp () {
mktemp || mktemp -t hist
} 2> /dev/null
# check we are using bash 4
(( BASH_VERSINFO[0] < 4 )) && error "This script can only be run by bash 4 or higher" 1
# An array to keep all the file sizes
declare -A file_sizes
declare -r tempfile=$(my_mktemp) || error "Cannot create tempfile" 2
# How wide is the terminal?
declare -ir term_cols=$(tput cols)
# Longest file name, Largest file, total file size
declare -i max_name_len=0 max_size=0 total_size=0
# A function to draw a line
drawline () {
declare line=""
declare char="-"
for (( i=0; i<$1; ++i )); do
line="${line}${char}"
done
printf "%s" "$line"
}
# This reads the output from du into an array
# And calculates total size and maximum size, max filename length
read_filesizes () {
while read -r size name; do
file_sizes["$name"]="$size"
(( total_size += size ))
(( max_size < size )) && (( max_size=size ))
(( max_file_len < ${#name} )) && (( max_file_len=${#name} ))
done
}
# run du to get filesizes
# Using a temporary file for output from du
{ du -d 0 */ || du --max-depth 0 *; } 2>/dev/null > "$tempfile"
read_filesizes < "$tempfile"
# The length for each line and percentage for each file
declare -i length percentage
# How many columns may the lines take up?
declare -i cols="term_cols - max_file_len - 10"
for k in "${!file_sizes[#]}"; do
(( length=cols * file_sizes[$k] / max_size ))
(( percentage=100 * file_sizes[$k] / total_size ))
printf "%-${max_file_len}s | %3d%% | %s\n" "$k" "$percentage" $(drawline $length)
done
printf "%d Directories\n" "${#file_sizes[#]}"
printf "Total size: %d blocks\n" "$total_size"
# clean up
rm "$tempfile"
exit 0
In the first and second line of the read_filesizes() function that I highlighted in bold, why are two variables (size name) being created if the name is being assigned to size in the array?
In the same function, (( max_size < size )) && (( max_size=size )) this line seems odd to me because how can the two expressions both be true?
Then in the first line of the for loop, (( **length=cols** * file_sizes[$k] / max_size )) I don't understand why the variable length is assigned to cols..why were they defined separately to begin with?
While I'm not 100% sure of the syntax, it seems clear enough to answer your questions :
First Question
why are two variables (size name) being created if the name is being assigned to size in the array?
It looks like name holds the file name and size holds the file size. Then the assignment file_sizes["$name"]="$size" stores the file sizes indexed by the file names.
Second Question
(( max_size < size )) && (( max_size=size ))
I believe this line assigns size to max_size if the previous value of max_size is smaller than size. The goal is that at the end max_size would hold the size of the largest file.
Third Question
(( length=cols * file_sizes[$k] / max_size ))
This calculates the length of the line that would be displayed for each file (whose goal is probably to illustrate the relative size of the file compared to the largest file). The length of the line is relative to the size of the file. cols is the length of the line that would be displayed for the largest file (the one whose size is max_size). cols = the lengh of the terminal - the length of the longest file name - 10.
This question already has answers here:
Variables in bash seq replacement ({1..10}) [duplicate]
(7 answers)
Brace expansion with a Bash variable - {0..$foo}
(5 answers)
Closed 8 years ago.
I'm making a program in bash that creates a histoplot, using numbers I have created. The numbers are stored as such (where the 1st number is how many words are on a line of a file, and the 2nd number is how many times this amount of words on a line comes up, in a given file.)
1 1
2 4
3 1
4 2
this should produce:
1 #
2 ####
3 #
4 ##
BUT the output I'm getting is:
1 #
2 #
3 #
4 #
however the for loop is not recognising that my variable "hashNo" is a number.
#!/bin/bash
if [ -e $f ] ; then
while read line
do
lineAmnt=${line% *}
hashNo=${line##* }
#VVVV Problem is this line here VVVV
for i in {1..$hashNo}
#This line ^^^^^^^ the {1..$hashNo}
do
hashes+="#"
done
printf "%4s" $lineAmnt
printf " $hashes\n"
hashes=""
done < $1
fi
the code works if I replace hashNo with a number (eg 4 makes 4 hashes in my output) but it needs to be able to change with each line (no all lines on a file will have the same amount of chars in them.
thanks for any help :D
A sequence expression in bash must be formed from either integers or characters, no parameter substitutions take place before hand. That's because, as per the bash doco:
The order of expansions is: brace expansion, tilde expansion, parameter, variable and arithmetic expansion and command substitution (done in a left-to-right fashion), word splitting, and pathname expansion.
In other words, brace expansion (which includes the sequence expression form) happens first.
In any case, this cries out to be done as a function so that it can be done easily from anywhere, and also made more efficient:
#!/bin/bash
hashes() {
sz=$1
while [[ $sz -ge 10 ]]; do
printf "##########"
((sz -= 10))
done
while [[ $sz -gt 0 ]]; do
printf "#"
((sz--))
done
}
echo 1 "$(hashes 1)"
echo 2 "$(hashes 4)"
echo 3 "$(hashes 1)"
echo 4 "$(hashes 2)"
which outputs, as desired:
1 #
2 ####
3 #
4 ##
The use of the first loop (doing ten hashes at a time) will almost certainly be more efficient than adding one character at a time and you can, if you wish, do a size-50 loop before that for even more efficiencies if your values can be larger.
I tried this for (( i=1; i<=$hashNo; i++ )) for the for loop, it seems to be working
Your loop should be
for ((i=0; i<hashNo; i++))
do
hashes+="#"
done
Also you can stick with your loop by the use of eval and command substitution $()
for i in $(eval echo {1..$hashNo})
do
hashes+="#"
done