I am developing a Linux's Bash shell script that extracts data from a text file leaving only the numbers.
These are my example parsed data:
3
4
4
5
6
7
8
8
9
11
I would like to create a simple text-mode bar chart like this one, but corresponding to these values:
Details:
I need the graphic chart to be vertical.
The first numbers should appear on the left, latest on the right.
A n (parsed number) characters high column is appropriate for me. So the first bar on the left in my example should be 3 characters high, the second 4, the third 4, the fourth 5, and so on.
More precisely, for this example, something (using the █character) like:
█
█
██
███
████
█████
██████
███████
█████████
██████████
██████████
██████████
Note the 3 characters high for the first (left) column and 11 characters high for the last (right) column.
Same example with $ characters, to make it more readable:
$
$
$$
$$$
$$$$
$$$$$
$$$$$$
$$$$$$$
$$$$$$$$$
$$$$$$$$$$
$$$$$$$$$$
$$$$$$$$$$
The closest I know of is my method for progress bar until now, that I have used in another scripts:
printf "\033[48;5;21m" # Blue background color
for i in $(seq 1 $n); do printf " "; done # Create bar using blue spaces
This is: filling each line printing a bar with n spaces. But this bar is horizontal, so it is not appropriate in this case.
I request some core loop example ideas to create this bar chart.
Under the suggestion of user Boardrider, solutions based on any unix-like tools are accepted.
Solutions based on scripting languages (like Perl or Python) for the Linux shell are accepted too, as long as they use to be implemented on many devices.
Here is a first and naive attempt... It's not a very efficient solution as data are parsed many times, but may it helps. In a way it's the first loop idea suggested by #Walter_A.
#!/bin/sh
#
## building a vertical bar graph of data file
## https://stackoverflow.com/q/30929012
##
## 1. required. Data file with one value per line and nothing else!
## /!\ provide the (relative or absolute) file path, not file content
: ${1:?" Please provide a file name"}
test -e "$1" || { echo "Sorry, can't find $1" 1>&2 ; exit 2 ; }
test -r "$1" || { echo "Sorry, can't access $1" 1>&2 ; exit 2 ; }
test -f "$1" || { echo "Sorry, bad format file $1" 1>&2 ; exit 2 ; }
test $( grep -cv '^[0-9][0-9]*$' "$1" 2>/dev/null ) -ne 0 || { echo "Sorry, bad data in $1" 1>&2 ; exit 3 ; }
# setting characters
## 2. optional. Ploting character (default is Dollar sign)
## /!\ for blank color use "\033[48;5;21m \033[0m" or you'll mess...
c_dot="$2"
: ${c_dot:='$'}
## 3. optional. Separator characher (default is Dash sign)
## /!\ as Space is not tested there will be extra characters...
c_sep="$3"
: ${c_sep:='-'}
# init...
len_w=$(wc -l "$1" | cut -d ' ' -f 1 )
l_sep=''
while test "$len_w" -gt 0
do
l_sep="${l_sep}${c_sep}";
len_w=$(($len_w-1))
done
unset len_w
# part1: chart
echo ".${c_sep}${l_sep}${c_sep}."
len_h=$(sort -n "$1" | tail -n 1)
nbr_d=${#len_h}
while test "$len_h" -gt 0
do
printf '| '
for a_val in $(cat "$1")
do
test "$a_val" -ge "$len_h" && printf "$c_dot" || printf ' '
done
echo ' |'
len_h=$(($len_h-1))
done
unset len_h
# part2: legend
echo "|${c_sep}${l_sep}${c_sep}|"
while test "$nbr_d" -gt 0
do
printf '| '
for a_val in $(cat "$1")
do
printf "%1s" $(echo "$a_val" | cut -c "$nbr_d")
done
echo ' |'
nbr_d=$(($nbr_d-1))
done
unset nbr_d
# end
echo "'${c_sep}${l_sep}${c_sep}'"
unset c_sep
exit 0
EDIT 1: Here is a rework on the script. It correct separators handling (just try with ' ' or '|' as third argument to see), but it may look less readable as I use arguments number instead of a additional variable.
EDIT 2: It also deals with negative integers... and you may change the ground (5th parameter)
#!/bin/sh
#
## building a vertical bar graph of data file
## https://stackoverflow.com/q/30929012
##
## 1. required. Data file with one value per line and nothing else!
## /!\ provide the (relative or absolute) file path, not file content
: ${1:?" Please provide a file name"}
[ -e "$1" ] || { echo "Sorry, can't find $1" 1>&2 ; exit 2 ; }
[ -r "$1" ] || { echo "Sorry, can't access $1" 1>&2 ; exit 2 ; }
[ -f "$1" ] || { echo "Sorry, bad format file $1" 1>&2 ; exit 2 ; }
[ $( grep -cv '^[-0-9][0-9]*$' "$1" 2>/dev/null ) -ne 0 ] || { echo "Sorry, bad data in $1" 1>&2 ; exit 3 ; }
## /!\ following parameters should result to a single character
## /!\ for blank color use "\033[48;5;21m \033[0m" or you'll mess...
## 2. optional. Ploting character (default is Dollar sign)
## 3. optional. Horizontal border characher (default is Dash sign)
## 4. optional. Columns separator characher (default is Pipe sign)
## (!) however, when no arg provided the graph is just framed in a table
## 5. optional. Ground level integer value (default is Zero)
test "${5:-0}" -eq "${5:-0}" 2>/dev/null || { echo "oops, bad parameter $5" 1>&2 ; exit 3 ; }
# init...
_long=$(wc -l < "$1" ) # width : number of data/lines in file
if [ -n "$4" ]
then
_long=$((_long*2-3))
fi
_line=''
while [ "$_long" -gt 0 ]
do
_line="${_line}${3:--}"
_long=$((_long-1))
done
unset _long
_from=$(sort -n "$1" | tail -n 1 ) # max int
_stop=$(sort -n "$1" | head -n 1 ) # min int
This rework comes in two flavors. The first produces an output like the previous one.
# begin
echo "${4-.}${3:--}${_line}${3:--}${4-.}"
# upper/positive
if [ $_from -gt ${5:-0} ]
then
while [ $_from -gt ${5:-0} ]
do
printf "${4:-| }"
for _cint in $(cat "$1" )
do
if [ $_cint -ge $_from ]
then
printf "${2:-$}$4"
else
printf " $4"
fi
done
echo " ${4:-|}"
_from=$((_from-1))
done
echo "${4-|}${3:--}${_line}${3:--}${4-|}"
fi
unset _from
# center/legend
_long=$(wc -L < "$1" ) # height : number of chararcters in longuest line...
while [ $_long -ge 0 ]
do
printf "${4:-| }"
for _cint in $(cat "$1" )
do
printf "%1s$4" $(echo "$_cint" | cut -c "$_long" )
done
echo " ${4:-|}"
_long=$((_long-1))
done
unset _long
# lower/negative
if [ $_stop -lt ${5:-0} ]
then
_from=${5:-0}
echo "${4-|}${3:--}${_line}${3:--}${4-|}"
while [ $_from -gt $_stop ]
do
printf "${4:-| }"
for _cint in $(cat "$1" )
do
if [ $_cint -lt $_from ]
then
printf "${2:-$}$4"
else
printf " $4"
fi
done
echo " ${4:-|}"
_from=$((_from-1))
done
fi
unset _stop
# end
echo "${4-'}${3:--}${_line}${3:--}${4-'}"
exit 0
Notice : There're two checks in order to avoid extra loop when all values are positive (above the ground) or negative (bellow the ground) !
Well, maybe I should always put the "center/legend" part at the end? It looks a bit ugly when there're both positive and negative values first, and when only negative integers it looks strange that labels don't read in the opposite and have unpleasant minus sign.
Also notice that wc -L is not POSIX... ...so another loop may be needed.
Here is another variant with legend number in the right size instead of the bottom.
Doing so, I save an extra loop but I don't really like the output (I prefer values on the left rather than the right side, but it's a taste isn't it ?)
# begin
printf "${4-.}${3:--}${_line}${3:--}${4-.}"
# upper/positive
if [ $_from -gt ${5:-0} ]
then
echo ""
while [ $_from -gt ${5:-0} ]
do
_ctxt=''
printf "${4:-| }"
for _cint in $(cat "$1" )
do
if [ $_cint -ge $_from ]
then
printf "${2:-$}$4"
else
printf " $4"
fi
if [ $_cint -eq $_from ]
then
_ctxt="_ $_from"
fi
done
echo " ${4:-}${_ctxt}"
_from=$((_from-1))
done
_from=$((_from+1))
else
echo "_ ${1}"
fi
# center/ground
if [ $_stop -lt ${5:-0} ] && [ $_from -gt ${5:-0} ]
then
echo "${4-|}${3:--}${_line}${3:--}${4-|}_ ${1}"
fi
# lower/negative
if [ $_stop -lt ${5:-0} ]
then
_from=${5:-0}
while [ $_from -gt $_stop ]
do
_ctxt=''
printf "${4:-| }"
for _cint in $(cat "$1" )
do
if [ $_cint -lt $_from ]
then
printf "${2:-$}$4"
else
printf " $4"
fi
if [ $_cint -eq $((_from-1)) ]
then
_ctxt="_ $_cint"
fi
done
echo " ${4:-|}${_ctxt}"
_from=$((_from-1))
done
fi
# end
unset _from
printf "${4-'}${3:--}${_line}${3:--}${4-'}"
if [ $_stop -lt ${5:-0} ]
then
echo ""
else
echo "_ ${1}"
fi
unset _stop
exit 0
EDIT 3: There're some extra checks so an extra ground line isn't added when there's only positive or negative numbers.
Finally, i think the final solution is a mix of both, where value are displayed on the side and the position of value in the center. Then it's more close to GNU Plot's output.
# init...
_long=$(wc -l < "$1" )
if [ -n "$4" ]
then
_long=$((_long*2-3))
fi
_line=''
while [ $_long -gt 0 ]
do
_line="${_line}${3:--}"
_long=$((_long-1))
done
unset _long
_from=$(sort -n "$1" | tail -n 1 ) # max int
_stop=$(sort -n "$1" | head -n 1 ) # min int
# begin
echo "${4-.}${3:--}${_line}${3:--}${4-.}"
# upper/positive
if [ $_from -gt ${5:-0} ]
then
while [ $_from -gt ${5:-0} ]
do
_ctxt=''
printf "${4:-| }"
for _cint in $(cat "$1" )
do
if [ $_cint -ge $_from ]
then
printf "${2:-$}$4"
else
printf " $4"
fi
if [ $_cint -eq $_from ]
then
_ctxt="_ $_from"
fi
done
echo " ${4:-|}$_ctxt"
_from=$((_from-1))
done
echo "${4-|}${3:--}${_line}${3:--}${4-|}"
fi
# center/ground
_size=$(wc -l < "$1" ) # width : number of data/lines in file
##_long=${#_size} # height : number of chararcters in long
#_long=1
##while [ $_long -gt 0 ]
#while [ $_long -le ${#_size} ]
#do
#_rank=1
#printf "${4:-| }"
#while [ $_rank -le $_size ]
#do
#printf "%1s$4" $( printf "%0${#_size}d" $_rank | cut -c $_long )
#_rank=$((_rank+1))
#done
#printf " ${4:-|}"
##_long=$((_long-1))
#_long=$((_long+1))
##if [ $_long -eq 0 ]
#if [ $_long -eq ${#_size} ]
#then
#printf "_ ${1}"
#fi
#echo ''
#done
_rank=1
printf "${4:-| }"
while [ $_rank -le $_size ]
do
printf "%1s$4" $( expr "$_rank" : '.*\(.\)$' )
_rank=$((_rank+1))
done
echo " ${4:-|}_ ${1}"
# lower/negative
if [ $_stop -lt ${5:-0} ]
then
echo "${4-|}${3:--}${_line}${3:--}${4-|}"
while [ $_from -gt $_stop ]
do
_ctxt=''
printf "${4:-| }"
for _cint in $(cat "$1" )
do
if [ $_cint -lt $_from ]
then
printf "${2:-$}${4}"
else
printf " $4"
fi
if [ $_cint -eq $((_from-1)) ]
then
_ctxt="_ $_cint"
fi
done
echo " ${4:-|}$_ctxt"
_from=$((_from-1))
done
fi
unset _from
unset _stop
# end
echo "${4-'}${3:--}${_line}${3:--}${4-'}"
exit 0
A last improvement would be the ability to scale...
Methinks it can be a bit simpler...
#!/bin/bash
# Asume the data is in a textfile "/tmp/testdata.txt". One value per line.
MAXVALUE=$( sort -nr /tmp/testdata.txt | head -n1 ) # get the highest value
CHARTHEIGHT=$MAXVALUE # use it for height of chart
# (can be other value)
CHARTLINE=$CHARTHEIGHT # variable used to parse the lines
while [ $CHARTLINE -gt 0 ]; do # start the first line
(( CHARTLINE-- ))
REDUCTION=$(( $MAXVALUE*$CHARTLINE/$CHARTHEIGHT )) # subtract this from the VALUE
while read VALUE; do
VALUE=$(( $VALUE-$REDUCTION ))
if [ $VALUE -le 0 ]; then # check new VALUE
echo -n " "
else
echo -n "▓▓▓ "
fi
done < /tmp/testdata.txt
echo
done
echo
exit
This script will parse the data every line, reduces the read value and checks if there's something left. If so, display a block; if not, display a space. Repeat every line with different REDUCTION value.
The script can be expanded to included a legend, colours, half/quarter blocks, etc...
Apart from SORT and HEAD it's all in BASH commands
You can install a dedicated tool for ploting...
I only know GNU-Plot, but it's big (with many dependencies including ImageMagick)
root#localhost: # apt-get install gnuplot-nox
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following extra packages will be installed:
fontconfig fontconfig-config fonts-droid fonts-liberation ghostscript groff
gsfonts hicolor-icon-theme imagemagick imagemagick-common libcairo2 libcroco3
libcupsimage2 libdatrie1 libdjvulibre-text libdjvulibre21 libexiv2-12 libffi5
libfontconfig1 libgd2-noxpm libgdk-pixbuf2.0-0 libgdk-pixbuf2.0-common
libglib2.0-0 libglib2.0-data libgs9 libgs9-common libice6 libijs-0.35
libilmbase6 libjasper1 libjbig0 libjbig2dec0 libjpeg8 liblcms1 liblcms2-2
liblensfun-data liblensfun0 liblqr-1-0 libltdl7 liblua5.1-0 libmagickcore5
libmagickcore5-extra libmagickwand5 libnetpbm10 libopenexr6 libpango1.0-0
libpaper-utils libpaper1 libpixman-1-0 libpng12-0 librsvg2-2 librsvg2-common
libsm6 libthai-data libthai0 libtiff4 libwmf0.2-7 libxaw7 libxcb-render0
libxcb-shm0 libxft2 libxmu6 libxpm4 libxrender1 libxt6 netpbm poppler-data
psutils shared-mime-info ttf-dejavu-core ufraw-batch x11-common
Suggested packages:
ghostscript-cups ghostscript-x hpijs gnuplot-doc imagemagick-doc autotrace
cups-bsd lpr lprng curl enscript ffmpeg gimp gnuplot grads hp2xx html2ps
libwmf-bin mplayer povray radiance sane-utils texlive-base-bin transfig
xdg-utils exiv2 libgd-tools libjasper-runtime liblcms-utils liblcms2-utils
ttf-baekmuk ttf-arphic-gbsn00lp ttf-arphic-bsmi00lp ttf-arphic-gkai00mp
ttf-arphic-bkai00mp librsvg2-bin poppler-utils fonts-japanese-mincho
fonts-ipafont-mincho fonts-japanese-gothic fonts-ipafont-gothic
fonts-arphic-ukai fonts-arphic-uming fonts-unfonts-core ufraw
The following NEW packages will be installed:
fontconfig fontconfig-config fonts-droid fonts-liberation ghostscript
gnuplot-nox groff gsfonts hicolor-icon-theme imagemagick imagemagick-common
libcairo2 libcroco3 libcupsimage2 libdatrie1 libdjvulibre-text libdjvulibre21
libexiv2-12 libffi5 libfontconfig1 libgd2-noxpm libgdk-pixbuf2.0-0
libgdk-pixbuf2.0-common libglib2.0-0 libglib2.0-data libgs9 libgs9-common
libice6 libijs-0.35 libilmbase6 libjasper1 libjbig0 libjbig2dec0 libjpeg8
liblcms1 liblcms2-2 liblensfun-data liblensfun0 liblqr-1-0 libltdl7 liblua5.1-0
libmagickcore5 libmagickcore5-extra libmagickwand5 libnetpbm10 libopenexr6
libpango1.0-0 libpaper-utils libpaper1 libpixman-1-0 libpng12-0 librsvg2-2
librsvg2-common libsm6 libthai-data libthai0 libtiff4 libwmf0.2-7 libxaw7
libxcb-render0 libxcb-shm0 libxft2 libxmu6 libxpm4 libxrender1 libxt6 netpbm
poppler-data psutils shared-mime-info ttf-dejavu-core ufraw-batch x11-common
0 upgraded, 73 newly installed, 0 to remove and 0 not upgraded.
Need to get 38.3 MB of archives.
After this operation, 111 MB of additional disk space will be used.
Do you want to continue [Y/n]?
(asking for "gnuplot" provide the same packages plus "gnuplot-nox")
Once installed, I check interactively with a data file just called data:
Terminal type set to 'unknown'
gnuplot> set term dumb
Terminal type set to 'dumb'
Options are 'feed size 79, 24'
gnuplot> plot data
undefined variable: data
gnuplot> plot "data"
gnuplot> # nice ASCII-chart not shown here, curve of "A"
gnuplot> set style data histogram
gnuplot> plot "data"
gnuplot> # nice ASCII-chart not shown here, bars of "**"
gnuplot> quit
For a quick start, look at this lowrank.net page
then histograms demo
and finaly, usage here
plus here to script the beast
For example :
#!/bin/sh
#
_max=$(sort -n "$1" | tail -n 1)
_min=$(sort -n "$1" | head -n 1)
_nbr=$(wc -l < "$1" )
gnuplot << EOF
set terminal dumb ${COLUMNS:-$(tput cols)},${LINES:-$(tput lines)}
set style data histogram
set xrange[-1:$(_nbr+1)]
set yrange[$(_min-3):$(_max+2)]
set xlabel "something to display below horizontal axis"
plot "$1" title "a nice title in the corner instead of filename"
EOF
Just change options to output a true graphic into a file.
And when there are very few options to set :
#!/bin/sh
#
gnuplot -e "set terminal dumb; set style data histogram; plot '$1' "
It may look overkill compared to shell script if there are only few data to plot.
But using such a tool becomes very usufull for a large amount of data (it runs faster)
or with some variety (GNUPlot escapes blank lines, plots both positive and negative integers and decimals, used multi-fields files, merge many files or columns in the same graph)
Finally, they're some interesting front-end to ease some use cases
eplot is a Ruby wrapper to pipe into it.
feedgnuplot is a Perl front-end that may look easier.
termplotlib, previously known as asciiplotlib, is a Python library over it.
EDIT: By default it uses screen size minus margin...
loop idea 1:
Search for the highest number and remember nr of lines.
Start with value=the highest number, end check all values -ge ${value}. These will be filled, other spaces. Next line you use (( value = value - 1 )).
Not very efficient, you will walk through the parsed data lot of times.
loop idea 2:
Create strings like "xxx" and "xxxxxxxx" from your data (and rememeber largest value). You have your chart horizantally)
Usee printf formatting for adding spaces to each line (you have a file with all lines the same length)
Search a way to pivot your file.
loop idea 3:
When you have k values with m as highest value, first create a file with
m lines of k sequential numbers (1 2 3 ...) and end the line with a space.
Loop through the values and replace the numbers with 'x' on the right place.
For value 6 in line 3 this would be something like
(( replace_from = m - 5 ))
sed -i ${replace_from}',$ s/ 6 / x /g' myfile
After the loop replace all numbers by spaces.
There're many graphing libraries for Python.
Python plotting libraries lists most of them.
However I think they are X oriented...
But some people wrote alternative solutions for console/terminal. I found (initially two, updated with others):
pysparklines and its elder spark to graph small histogram of input using Unicode characters (so it's nice but limited, as it's a single line, and renders better under X or a console with UTF-8 font)
bashplotlib - despite its name is a pythonic collection that can draw histograms and scatters in pure terminal/console in a similar way to Gnuplot.
plotext can draw histograms and scatters in terminal.
termgraph can draw histograms and heatmap, plus has ability to use colours (with colorama) or custom ticks.
YouPlot (with command uplot and graph type as subcommand) is a new challenger in Ruby…
Update February 2023: they're some interesting libraries you can use interactively or within your Python code.
terminalplot and termplot are very simple and ASCII term oriented, with no dependency. They're easy to use too. But termplot does only vertical bars (like the images in the question.)
uniplot is a more capable because of heavy use of Unicode (hence better under X with UTF-8 font) and NumPy as sole dependency.
termplotlib seems to be similar to the previous but with no Unicode related. It also depends on NumPy and gunplots (it's a big/heavy front-end then.) It has a nice cousin, termtables for tables, but that's another topic.
plotext also claims no dependency and is very capable. However I suspect it needs an advanced terminal, not a dumb ASCII one, for some advanced rendering. Anyway, it's similar to mathplotlib which in-opposition is X oriented (workaround may be in use of mathplotlib-backend-kitty or mathplotlib-sixel for example.)
plotille is a generic graphing similar to gunplot or mathplotlib, but internally build with canvas available for any nice braille-dot-art too. Dependencies are: NumPy, Pendulum, Mock, Pillow, Flake8.
I have issue with an if statement. In WEDI_RC is saved log file in the following format:
name_of_file date number_of_starts
I want to compare first argument $1 with first column and if it is true than increment number of starts. When I start my script it works but just with one file, eg:
file1.c 11:23:07 1
file1.c 11:23:14 2
file1.c 11:23:17 3
file1.c 11:23:22 4
file2.c 11:23:28 1
file2.c 11:23:35 2
file2.c 11:24:10 3
file2.c 11:24:40 4
file2.c 11:24:53 5
file1.c 11:25:13 1
file1.c 11:25:49 2
file2.c 11:26:01 1
file2.c 11:28:12 2
Every time when I change file it begin counts from 1. I need to continue with counting when it ends.
Hope you understand me.
while read -r line
do
echo "line:"
echo $line
if [ "$1"="$($line | grep ^$1)" ]; then
number=$(echo $line | grep $1 | awk -F'[ ]' '{print $3}')
else
echo "error"
fi
done < $WEDI_RC
echo "file"
((number++))
echo $1 `date +"%T"` $number >> $WEDI_RC
There are at least two ways to resolve the problem. The most succinct is probably:
echo "$1 $(date +"%T") $(($(grep -c "^$1 " "$WEDI_RC") + 1))" >> "$WEDI_RC"
However, if you want to have counts for each file separately, you can do that using an associative array, assuming you have Bash version 4.x (not 3.x as is provided on Mac OS X, for example). This code assumes the file is correctly formatted (so that the counts do not reset to 1 each time the file name changes).
declare -A files # Associative array
while read -r file time count # Split line into three variables
do
echo "line: $file $time $count" # One echo - not two
files[$file]="$count" # Record the current maximum for file
done < "$WEDI_RC"
echo "$1 $(date +"%T") $(( ${files[$1]} + 1 ))" >> "$WEDI_RC"
The code uses read to split the line into three separate variables. It echoes what it read and records the current count. When the loop's done, it echoes the data to append to the file. If the file is new (not mentioned in the file yet), then you will get a 1 added.
If you need to deal with the broken file as input, then you can amend the code to count the number of entries for a file, instead of trusting the count value. The bare-array reference notation used in the (( … )) operation is necessary when incrementing the variable; you can't use ${array[sub]}++ with the increment (or decrement) operator because that evaluates to the value of the array element, not its name!
declare -A files # Associative array
while read -r file time count # Split line into three variables
do
echo "line: $file $time $count" # One echo - not two
((files[$file]++)) # Count the occurrences of file
done < "$WEDI_RC"
echo "$1 $(date +"%T") $(( ${files[$1]} + 1 ))" >> "$WEDI_RC"
You can even detect whether the format is in the broken or fixed style:
declare -A files # Associative array
while read -r file time count # Split line into three variables
do
echo "line: $file $time $count" # One echo - not two
if [ $((files[$file]++)) != "$count" ]
then echo "$0: warning - count out of sync: ${files[$file]} vs $count" >&2
fi
done < "$WEDI_RC"
echo "$1 $(date +"%T") $(( ${files[$1]} + 1 ))" >> "$WEDI_RC"
I don't get exactly what you want to achieve with your test [ "$1"="$($line | grep ^$1)" ] but it seems you are checking that the line start with the first argument.
If it is so, I think you can either:
provide the -o option to grep so that it print just the matched output (so $1)
use [[ "$line" =~ ^"$1" ]] as test.
I need to write a Bash script that source another script (config script) for hours. If the hour mentioned in config script matches the Linux past hour it needs to print the hour.
$ cat ConfHours.sh
#!/bin/bash --
Hours=(0 1 2 22 23)
$ cat Foo.sh
#!/bin/bash --
source /home/Geo/ConfHours.sh
arrayHours=( ${HOURS} )
for v in "${arrayHours[#]}"
do
HOUR=$(( $(date +%H) -1))
if [ "${HOUR}" == "v" ] ; then
HOUR = ${HOUR}
echo $HOUR
fi
done
When I run Foo.sh, I do not get anything. Could you please correct me where I am wrong?
Some errors:
source /home/Geo/ConfHours.sh
arrayHours=( ${HOURS} )
ConfHours defines a variable named Hours -- different variable
for v in "${arrayHours[#]}"
do
HOUR=$(date -d "1 hour ago" +%H)
You don't need to define this every time through the loop: put it before the for statement
if [ "${HOUR}" == "v" ] ; then
missing $ for the v variable
$HOUR will contain a leading 0 (due to %H)
a better test: if (( 10#$HOUR == 10#$v ))
HOUR = ${HOUR}
No spaces around the = allowed for variable assignment. Why are you trying to redefine the variable to itself?
echo $HOUR
fi
done
A more concise way to test an array contains a value is to take advantage of array string concatenation and pattern matching:
source ConfHours.sh
hour=$(date +%k) # leading space, not leading zero
if [[ " ${Hours[*]} " == *" ${hour# } "* ]]; then
echo "$hour"
fi
All spaces and quotes are required.
Don't use UPPER_CASE_VARS: here's why
I have some operations to do on files last modified on a specific date. I would like to get the date, stock it in a string, then split it to test if the day corresponds to what I want.
So far, I've been trying things like that:
#!/bin/bash
for i in {45..236}; do
nom=M$i
chem=/Users/nfs/helene/soft/metAMOS-1.5rc3/$nom.fastq/Assemble/out
if [ -e $chem ]; then
IN= $(date -r $chem)
arr=(${IN//\ / })
if [[ ${arr[1]} == 'juin' && ${arr[2]} == '10' ]]; then
echo $nom
#cp $chem/proba.faa /Users/nfs/helene/metagenomes/DB/$nom.faa
fi
fi
done
exit 0
But it seems like the date isn't well stocked in $IN, and I'm not sure about the space-spliting either..
Perhaps the simple mistake is that you didn't place your assignment adjacent to =. It must be:
IN=$(date -r $chem)
And here's a simplified suggestion:
#!/bin/bash
for i in {45..236}; do
nom="M${i}"
chem="/Users/nfs/helene/soft/metAMOS-1.5rc3/${nom}.fastq/Assemble/out"
if [[ -e $chem ]]; then
read month day < <(exec date -r "$chem" '+%b %d')
if [[ $month == 'Jun' && $day == 10 ]]; then
echo "$nom"
# cp "$chem/proba.faa" "/Users/nfs/helene/metagenomes/DB/$nom.faa"
fi
fi
done
exit 0
* See date --help for a list of formats.
* <() is a form of Process Substitution. Check Bash's manual for it.
* Always place your arguments around double quotes when they have variables to avoid word splitting.
I am trying to write a bash script which takes in a file as $1 and then copies it and appends the year,month, and date onto the end of the file. So for example if i had a file foo.txt it would turn into foo.txt.2010.11.16. I'm not really 100% sure how to do this, any suggestions?
Simple version:
#!/bin/bash
cp "$1" "$1".`date +%Y.%m.%d`
Fancier version:
#!/bin/bash
#
# date_tag_files file1 [file2 file3 ...]
#
for f in $*
do
cp "$f" "$f".`date +%Y.%m.%d`
done
You can do this by using $(date ...) to get the current date into an environment variable.
The following script allows you to pass in any number of file names and it will attempt to copy all of them to similar files with the date appended.
It gets the date once in case you try to run it around midnight and you want them all to get the same date even if you cross into the following day during the process.
#!/usr/bin/bash
if [[ $# -lt 1 ]] ; then
echo 'Usage: datecp <filename> ...'
exit 1
fi
dt=$(date +%Y.%m.%d)
while [[ $# -ne 0 ]] ; do
if [[ ! -f "$1" ]] ; then
echo 'Warning:' $1 'is not a regular file, not copied'
else
newf="$1.$dt"
cp "$1" "$newf"
echo "$1" '-->' "$newf"
fi
shift
done
The output for the last one is along the lines of:
pax> datecp
Usage: datecp <filename> ...
pax> datecp xyz
xyz --> xyz.2010.11.17
pax> datecp xyz abc
xyz --> xyz.2010.11.17
abc --> abc.2010.11.17
pax> ./qq.sh xyz qwert abc
xyz --> xyz.2010.11.17
Warning: qwert is not a regular file, not copied
abc --> abc.2010.11.17