I'm working on a file that contains info such as:
service.txt
service1 - info1
service2 - info2
service3 - info3
...
I added each line of the file to an array. Subsequently with the select command I want to query the elements of the array displaying only the "serviceN" and display the "info" once the element has been selected.
At the moment I can't cut the line to display only the "service"
`
#File example
#service.txt
#service1 - info1
#service2 - info2
#...
#serviceN - infoN
#!/bin/bash
file='service.txt'
line=()
while read -r line; do
line+=($line)
done < $file
echo "Select the service..."
select line in ${line[#]}; do # how can i use "cut" here?
echo $line
break
done
exit 0
You don't need the cut for this particular problem. In pure bash:
#!/bin/bash
readarray -t lines < service.txt
echo "Select the service..." >&2
select service in "${lines[#]%%[[:blank:]]*}"; do
echo "$service"
break
done
For the "${lines[#]%%[[:blank:]]*}", see Shell Parameter Expansion, paragraph starting with ${parameter%word}.
Related
I know that reading a .csv file can be done simply in bash with this loop:
#!/bin/bash
INPUT=data.cvs
OLDIFS=$IFS
IFS=,
[ ! -f $INPUT ] && { echo "$INPUT file not found"; exit 99; }
while read flname dob ssn tel status
do
echo "Name : $flname"
echo "DOB : $dob"
echo "SSN : $ssn"
echo "Telephone : $tel"
echo "Status : $status"
done < $INPUT
IFS=$OLDIFS
But I want to slightly modify this- I want to make the columns be defined by the programmer in the bash file.
For example:
declare -a columns=("Name", "Surname", "ID", "Gender")
while read columns
do
//now echo everything that has been read
done < $INPUT
So I want to specify the list of variables that should be used as the container to the read CSV data with an array and then access this array inside the while body.
Is there a way to do it?
The key to this solution is the comment before the while statement below. read is a built-in, but it is still a command, and command arguments are expanded by the shell before executing the command. After expansion of ${columns[#]}, the command becomes
read Name Surname ID Gender
Example:
# Don't use commas in between array values (since they become part of the value)
# Values not quoted because valid names don't need quotes, and these
# value must be valid names
declare -a columns=(Name Surname ID Gender)
Then, we can try:
# Read is a command. Arguments are expanded.
# The quotes are unnecessary but it's hard to break habits :)
while read "${columns[#]}"; do
echo Name is "$Name"
# etc
done <<< "John Doe 27 M"
Output:
Name is John
This same approach would work even in a shell without arrays; the column names can just be a space separated list. (Example run in dash, a Posix shell)
$ columns="Name Surname ID Gender"
$ # Here it is vital that $columns not be quoted; we rely on word-splitting
$ while read $columns; do
> echo Name is $Name
> done
John Doe 27 M
Name is John
...
Read the line into an array, then loop through that array and create an associative array that uses the column names.
while read -r line
do
vals=($line)
declare -A colmap
i=0
for col in ${columns[#]}
do
colmap[col]=${vals[$i]}
let i=i+1
done
# do stuff with colmap here
# ...
unset colmap # Clear colmap before next iteration
done < $INPUT
I have a command, when I run it, it output a table that looks like;
Id Name File OS Version Annotation
10 MICKEY [MICKEY_01_001] MICKEY/MICKEY.vmx windows8Server64Guest vmx-08
13 DONALD [DONALD_01_001] DONALD/DONALD.vmx windows7Server64Guest vmx-10
2 GOOFY [GOOFY_01_001] GOOFY/GOOFY.vmx windows9Server64Guest vmx-09
I then store the table in an array call TABLE and list the TABLE array, the code looks like this;
readarray -t TABLE <<< "$(command)"
IFS='|'
for i in "${TABLE[#]}"
do
echo $I
done
How do I append to the end of each array element? I want the table to be presented as following;
Id Name File OS Version Annotation
10 MICKEY [MICKEY_01_001] MICKEY/MICKEY.vmx windows8Server64Guest vmx-08 ON
13 DONALD [DONALD_01_001] DONALD/DONALD.vmx windows7Server64Guest vmx-10. OFF
2 GOOFY [GOOFY_01_001] GOOFY/GOOFY.vmx windows9Server64Guest vmx-09. ON
If you want to append ON or OFF in your array
readarray -t TABLE <<< "$(command)"
#IFS='|' why ?
for ((i=1;i<"${#TABLE[#]}";i++))
# start i=1 to preserve header
do
# condition to ON or OFF
[ "${a:=OFF}" = 'ON' ] &&a='OFF'||a='ON'
TABLE["$i"]="${TABLE["$i"]} $a"
done
for i in "${TABLE[#]}"
do
echo "$i"
done
What does the command "$(command)" do? Should we assume, that one line of the output = one string = one element of the array? If so, then this should work for you:
readarray -t TABLE <<< "$(command)"
IFS='|'
for i in "${TABLE[#]}"
do
if <condition_for_on_met>; then
echo "$i ON"
elif <condition_for_off_met>;then
echo "$i OFF"
else
echo "$i"
fi
done
But this is a general answer. You could improve your question by showing us what your input is and how is it processed before it is printed.
The structure of the my input files are as follows:
<string1> <string2> <stringN>
hello nice world
one three
NOTE:, the second row has a tab/null on the second column. so second column on second row is empty and not 'three'
In bash, I want to loop through each row and also be able to process each individual column (string[1-N])
I am able to iterate to each row:
#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
line=${line/$/'\t'/,}
read -r -a columns <<< "$line"
echo "current Row: $line"
echo "column[1]: '${columns[1]}'"
#echo "column[N] '${columns[N]}'"
done < "${1}"
Expected result:
current Row: hello,nice,world
column[1]: 'nice'
current Row: one,,three
column[1]: ''
Basically what I do is iterate through the input file (here passed as argument), do all the "cleaning" like prevents whitespace from being trimmed, ignore backslashes an consider also the last line.
then I replace the tabs '\t' by a comma
and finally read the line into an array (columns) to be able to select a particular column.
The input file has tabs as separator value, so I tried to convert it to csv format, I am not sure if the regex I use is correct in bash, or something else is wrong because this does not return a value in the array.
Thanks
You are almost there, a little fix on the on translating '\t' to commas and you have to set also IFS to be the comma.
try this:
#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
line=${line//$'\t'/,}
IFS=',' read -r -a columns <<< "$line"
#echo "current Row: $line"
echo "column[0]:'${columns[0]}' column[1]:'${columns[1]}' column[2]:'${columns[2]}'"
done < "${1}"
run:
$> <the_script> <the_file>
Outputs:
column[0]:'hello' column[1]:'nice' column[2]:'world '
column[0]:'one' column[1]:'' column[2]:'three'
First let me say I followed questions on stackoverflow.com that relate to my question and it seems the rules are not applying. Let me show you.
The following script:
#!/bin/bash
OUTPUT_DIR=/share/es-ops/Build_Farm_Reports/WorkSpace_Reports
TODAY=`date +"%m-%d-%y"`
HOSTNAME=`hostname`
WORKSPACES=( "bob" "mel" "sideshow-ws2" )
if ! [ -f $OUTPUT_DIR/$HOSTNAME.csv ] && [ $HOSTNAME == "sideshow" ]; then
echo "$TODAY","$HOSTNAME" > $OUTPUT_DIR/$HOSTNAME.csv
echo "${WORKSPACES[0]}," >> $OUTPUT_DIR/$HOSTNAME.csv
sed -i "/^'"${WORKSPACES[0]}"'/$/'"${WORKSPACES[1]}"'/" $OUTPUT_DIR/$HOSTNAME.csv
sed -i "/^'"${WORKSPACES[1]}"'/$/${WORKSPACES[2]}"'/" $OUTPUT_DIR/$HOSTNAME.csv
fi
I want the output to look like:
09-20-14,sideshow
bob,mel,sideshow-ws2
the sed statements are supposed to append successive array elements to preceding ones on the same line. Now I know there's a simpler way to do this like:
echo "${WORKSPACES[0]},${WORKSPACES[1]},${WORKSPACES[2]}" >> $OUTPUT_DIR/$HOSTNAME.csv
But let's say I had 30 elements in the array and I wanted to appended them one after the other on the same line? Can you show me how to loop through the elements in an array and append them one after the other on the same line?
Also let's say I had the output of a command like:
df -m /export/ws/$ws | awk '{if (NR!=1) {print $3}}'
and I wanted to append that to the end of the same line.
But when I run it I get:
+ OUTPUT_DIR=/share/es-ops/Build_Farm_Reports/WorkSpace_Reports
++ date +%m-%d-%y
+ TODAY=09-20-14
++ hostname
+ HOSTNAME=sideshow
+ WORKSPACES=("bob" "mel" "sideshow-ws2")
+ '[' -f /share/es-ops/Build_Farm_Reports/WorkSpace_Reports/sideshow.csv ']'
And the file right now looks like:
09-20-14,sideshow
bob,
I am happy to report that user syme solved this (see below) but then I realized I need the date in the first column:
09-7-14,bob,mel,sideshow-ws2
Can I do this using syme's for loop?
Okay user syme solved this too he said "Just add $TODAY to the for loop" like this:
for v in "$TODAY" "${WORKSPACES[#]}"
Okay now the output looks like this I changed the elements in the array btw:
sideshow
09-20-14,bob_avail,bob_used,mel_avail,mel_used,sideshow-ws2_avail,sideshow-ws2_used
Now below that the next line will be populated by a , in the first column skipping the date and then:
df -m /export/ws/$v | awk '{if (NR!=1) {print $3}}
which equals the value of available space on bob in the first iteration
and then:
df -m /export/ws/$v | awk '{if (NR!=1) {print $2}}
which equals the value of used space on bob in the 2nd iteration
and then we just move on to the next value in ${WORKSPACE[#]}
which will be mel and do the available and used as we did with bob or $v above.
I know you geniuses on here will make child's play out of this.
I solved my own last question on this thread:
WORKSPACES2=( "bob" "mel" "sideshow-ws2" )
separator="," # defined empty for the first value
for v in "${WORKSPACES2[#]}"
do
available=`df -m /export/ws/$v | awk '{if (NR!=1) {print $3}}'`
used=`df -m /export/ws/$v | awk '{if (NR!=1) {print $2}}'`
echo -n "$separator$available$separator$used" >> $OUTPUT_DIR/$HOSTNAME.csv # append, concatenated, the separator and the value to the file
done
produces:
sideshow
09-20-14,bob_avail,bob_used,mel_avail,mel_used,sideshow-ws2_avail,sideshow-ws2_used
,470400,1032124,661826,1032124,43443,1032108
echo -n permits to print text without the linebreak.
To loop over the values of the array, you can use a for-loop:
echo "$TODAY,$HOSTNAME" > $OUTPUT_DIR/$HOSTNAME.csv # with a linebreak
separator="" # defined empty for the first value
for v in "${WORKSPACES[#]}"
do
echo -n "$separator$v" >> $OUTPUT_DIR/$HOSTNAME.csv # append, concatenated, the separator and the value to the file
separator="," # comma for the next values
done
echo >> $OUTPUT_DIR/$HOSTNAME.csv # add a linebreak (if you want it)
I am trying to parse the output on svn info without resorting to an external shell command like sed or awk. This is purely academic as I know I could do this in a heartbeat with those tools.
Output I am parsing is:
Path: .
URL: svn://brantwinter#192.168.10.222/handbrake_batch/trunk/handbrake
Repository Root: svn://ilium007#192.168.10.222/handbrake_batch
Repository UUID: 99c2cca7-102b-e211-ab20-02060a000c0b
Revision: 6
Node Kind: directory
Schedule: normal
Last Changed Author: ilium007
Last Changed Rev: 6
Last Changed Date: 2012-11-10 19:00:35 +1000 (Sat, 10 Nov 2012)
Here is my code:
#!/bin/bash
#set -x
OLD_IFS="$IFS"
IFS=$'\r\n'
# Get the output from svn info into an array
SVN_INFO_ARR=(`svn info`)
COUNT=0
for i in ${SVN_INFO_ARR[#]}; do
echo $COUNT
echo "$i"
(( COUNT++ ))
done
# Get the element that says "Revision: 6"
REV_ARR=${SVN_INFO_ARR[4]}
# Testing the loop over what should be a two element array
COUNT=0
for i in ${REV_ARR[#]}; do
echo $COUNT
echo "$i"
(( COUNT++ ))
done
#This should give the number 6 (or string or something)
REV_NUMBER=${REV_ARR[1]}
echo ${REV_NUMBER}
### INCREMENT REVISION NUMBER FROM ARRAY ELEMENT ###
#NEW_REV_NUMBER= ????? + 1
IFS="$OLD_IFS"
I would like to be able to take the string:
Revision: 6
and pull out the 6 and increment by 1 so I can update a release txt file to be included in the SVN commit.
I have tried to make that 6 turn into a 7 for an hour now and feel like an idiot because I can't do it.
You need parenthesis: Change this:
# Get the element that says "Revision: 6"
REV_ARR=${SVN_INFO_ARR[4]}
to this:
# Get the element that says "Revision: 6"
REV_ARR=(${SVN_INFO_ARR[4]})
before
#This should give the number 6 (or string or something)
REV_NUMBER=${REV_ARR[1]}
so you'll be able to:
((REV_NUMBER++))
Edit:
As you wrote:
SVN_INFO_ARR=(`svn info`)
instead of just:
SVN_INFO_ARR=`svn info`
The parenthesis is used in bash to define an array. Have a look at:
man -Len -P'less +"/^ *Arrays"' bash
Instead of hardcoding the array indices, a better way would be to filter out the line you need and extract the number
Here's one way using regex (Bash 4)
while read -r line; do
if [[ $line =~ Revision:\ ([0-9]+) ]]; then
new_rev_num=$((BASH_REMATCH[1]+1))
echo $new_rev_num
break
fi
done < $(svn info)
Use grep to only select the line you need. Then, use Parameter expansion to remove "Revision: ". Finally, use let to do the arithmetics:
REVISION=$(svn info | grep '^Revision:')
REVISION=${REVISION#* }
let REVISION++
This code worked in the end:
#!/bin/bash
set -x
OLD_IFS="$IFS"
IFS=$'\r\n'
# Get the output from svn info into an array
SVN_INFO_ARR=(`svn info`)
IFS="$OLD_IFS"
# Get the element that says "Revision: 6"
REV_ARR=(${SVN_INFO_ARR[4]})
#This should give the number 6 (or string or something)
REV_NUMBER=${REV_ARR[1]}
echo $REV_NUMBER
echo $(( REV_NUMBER + 1 ))
The answer above had me stumped for a while because it was missing the $ in front of:
echo $(( REV_NUMBER + 1 ))
and the ((REV_NUMBER++)) notation did not work, I still got 6, not 7:
+ OLD_IFS='
'
+ IFS='
'
+ SVN_INFO_ARR=(`svn info`)
++ svn info
+ IFS='
'
+ REV_ARR=(${SVN_INFO_ARR[4]})
+ REV_NUMBER=6
+ echo 6
6
+ echo 6
6