Vim startup project files - bash script - bash

This program is supposed to open 3 vim terminals with 3 files opened in each one. I have a startup.txt file in each subfolder with the name of each file that I want to startup. ( one name on each line )
Problems that need to be fixed:
How can I make the command on line 22 executable? It gives me the following errors when I try to run this command ( it opens the first file in each startup.txt though )
Error:
/home/george/bin/project: line 17: vsp: command not found
/home/george/bin/project: line 19: vsp: command not found
/home/george/bin/project: line 17: vsp: command not found
/home/george/bin/project: line 19: vsp: command not found
Updated code:
1 #!/bin/bash
2
3 ADDR_FILE=~/Documents/CEDA #address to the file
4 ADDR_CEDA=$ADDR_FILE/ceda_lib #address for ceda_lib
5 ADDR_GENERAL=$ADDR_FILE/general #address for general
6 ADDR_CLI=$ADDR_FILE/CLI #address for CLI
7
8 function getFile
9 {
10 awk 'NR=='$1 $2/startup.txt
11 }
12
13 for i in $ADDR_CEDA $ADDR_GENERAL $ADDR_CLI; do
14 CMD_2=""
15 CMD_3=""
16 if [ $(getFile 2 $i) ]; then #checks if line 2 isn't blank
17 CMD_2=-c "vsp $i/$(getFile 2 $i)"
18 if [ $(getFile 3 $i) ]; then #checks if line 3 isn't blank
19 CMD_3=-c "vsp $i/$(getFile 3 $i)"
20 fi
21 fi
22 gnome-terminal -e "vim $i/$(getFile 1 $i) $CMD_2 $CMD_3"
23 done
Example startup.txt:
file1.cpp
file2.hpp
file3.hpp
Please note that I'm a beginner in bash scripting. Any general advice would be greatly appreciated.

I'm not sure what you are trying to do with vim exactly as I don't use that syntax, but here are some hints.
Your getline() function can easily be replaced with a simple sed command. If file contains:
line 1
line 2
line 3
Try running:
sed '2!d' file
and you will see it deletes all lines other than line 2. So, your vim command starts to look like this:
vim -c vsp $i/$(sed '2!d' $i)
If you want three xterms, you need to start three, and tell each one to execute a new vim. If you want them all running at once, you will need to background them with & at the end. That means your script is starting to look like this:
for i in "file1" "file2" "file3" ; do
xterm -e "vim -c vsp $i/$(sed '2!d' $i) ... " &
done

Working code:
1 #!/bin/bash
2
3 ADDR_FILE=~/Documents/CEDA #address to the file
4 ADDR_CEDA=$ADDR_FILE/ceda_lib #address for ceda_lib
5 ADDR_GENERAL=$ADDR_FILE/general #address for general
6 ADDR_CLI=$ADDR_FILE/CLI #address for CLI
7 ADDRESSES="$ADDR_CEDA $ADDR_GENERAL $ADDR_CLI"
8
9 if [ $1 ]; then #checks for parameters
10 if [ $1 = "-f" ] || [ $1 = "--files" ]; then
11 for i in $ADDRESSES; do
12 nautilus $i &
13 done
14 exit 0 #end script
15 fi
16 fi
17
18 function getFile
19 {
20 awk 'NR=='$1 $2/startup.txt
21 }
22
23 for i in $ADDRESSES; do
24 CMD_2=""
25 CMD_3=""
26 if [ $(getFile 2 $i) ]; then #checks if line 2 isn't blank
27 CMD_2="-c \"vsp $i/$(getFile 2 $i)\""
28 if [ $(getFile 3 $i) ]; then #checks if line 3 isn't blank
29 CMD_3="-c \"vsp $i/$(getFile 3 $i)\""
30 fi
31 fi
32 gnome-terminal -e "vim $i/$(getFile 1 $i) $CMD_2 $CMD_3"
33 done
I will add more to it over time but it does the bare minimum of what I wanted it to do :D Thanks for all your help guys!
PS: I've edited my question quite some times.

Related

Cannot print in awk command in bash script

I am trying to read values from a file and print specific items into a variable which I will use later.
cat /dir1/file1 | while read blmbline2
do
BLMBFILE2=`print $blmbline2 | awk '{$1=""; print $0}'`
echo $BLMBFILE2
done
When I run that same code at the command line, it runs as expected, but, when I run it in a bash script called testme.sh, I get this error:
./testme.sh: line 3: print: command not found
If I run print by itself at the command prompt, I don't get an error (just a blank line).
If I run "bash" and then print at the command prompt, I get command not found.
I can't figure out what I'm doing wrong. Can someone suggest?
updated: I see some other posts that say to use echo or printf? Is there a difference I need to be concerned with in using one of those in bash?
Since awk can read files, you may be able to do away with the cat | while read and just use awk. Using a sample file containing:
1 2 3 4 5 6
1 2 3 4 5 6
1 2 3 4 5 6
1 2 3 4 5 6
1 2 3 4 5 6
1 2 3 4 5 6
Declare your bash array variable and populate with the output from awk:
arr=() ; arr=($(awk '{$1=""; print $0}' /dir1/file1))
Use the following to display array size and contents:
printf "array length: %d\narray contents: %s\n" "${#arr[#]}" "${arr[*]}"
Output:
array length: 30
array contents: 2 3 4 5 6 2 3 4 5 6 2 3 4 5 6 2 3 4 5 6 2 3 4 5 6 2 3 4 5 6
Change print to echo in your shell script. With printf you can format the data and with echo it will print the entire line of the file. Also, create an array so you can store multiple items:
BLMBFILE2=()
while IFS= read -r -d $'\0'
do
BLMBFILE2+=(`echo $REPLY | awk '{$1=""; print $0}'`)
echo $BLMBFILE2
done < <(cat /dir1/file1)
echo "Items found:"
for value in "${BLMBFILE2[#]}"
do
echo $value
done

return values in next line in file on every invocation of script. MIDI Command line sequencer

So i have a file "scenes.txt", containing a 2-column set of numbers, about 500 lines in total:
0 01
6 22
5 03
4 97
7 05
3 98
2 99
9 00 etc...
First column range is 0-9, second column range is 0-99. The 2 fields are tab-separated.
On each invocation of the shell script, I want the script to output the set of the 2 numbers from the next line in the list to 2 variables.
So if I run the script for the first time, var1 should be 0 and var2 should be 01, on the next run of the script var1 should be 6 and var2 should be 22, and so on...
These values are going to the sendmidi command, https://github.com/gbevin/SendMIDI, to control a piece of hardware via MIDI.
You could call it a clumsy MIDI command line sequencer.
what I tried so far: I created a bash script "test1":
#!/bin/bash
read LINECNT < pointer
echo "next line to be processed: $LINECNT"
VAR1=$(sed "${LINECNT}q;d" scenes.txt)
echo "Line and Variables: $LINECNT: $VAR1"
# here the actual magic happens:
/usr/local/bin/sendmidi dev "USB2.0-MIDI Anschluss 2" cc 32 $VAR1 PC $VAR2
((LINECNT++))
echo $LINECNT > pointer
and another file named "pointer", which just holds the pointer position for the value to output upon next invocation.
File pointer:
1
This results in:
[dirk#dirks-MacBook-Pro] ~
❯ ./test2
next line to be processed: 1
Line and Variables: 1: 0 01
[dirk#dirks-MacBook-Pro] ~
❯ ./test2
next line to be processed: 2
Line and Variables: 2: 6 22
[dirk#dirks-MacBook-Pro] ~
❯ ./test2
next line to be processed: 3
Line and Variables: 3: 5 03
[dirk#dirks-MacBook-Pro] ~
❯ ./test2
next line to be processed: 4
Line and Variables: 4: 4 97
[dirk#dirks-MacBook-Pro] ~
❯ ./test2
next line to be processed: 5
Line and Variables: 5: 7 05
My Problem:
There are two Values in each line of "scenes.txt", but the
VAR=$(sed "${LINECNT}q;d" scenes.txt) line only gives the whole line with both values.
I tried several "cut" modifications, like this
VAR1=cut -f1 - $(sed "${LINECNT}q;d" scenes.txt)
VAR2=$(cut -f2 $(sed "${LINECNT}q;d" scenes.txt))
but with no luck...
How do I push the two values from that line into VAR1 and VAR2?
Your sed commands are incorrect. Also, the variables can be read with a single read command:
#!/bin/bash
read -r linecnt < pointer
read -r var1 var2 < <(sed "$linecnt!d; q" scenes.txt)
/usr/local/bin/sendmidi dev "USB2.0-MIDI Anschluss 2" cc 32 "$var1" PC "$var2"
echo $((linecnt + 1)) > pointer
I changed your variable names to lower case since, by convention, capitalized variables are environment variables and internal shell variables.
IFS='\t'
arr=($VAR1)
/usr/local/bin/sendmidi dev "USB2.0-MIDI Anschluss 2" cc 32 ${arr[0]} PC ${arr[1]}

How to nest loops correctly

I have 2 scripts, #1 and #2. Each work OK by themselves. I want to read a 15 row file, row by row, and process it. Script #2 selects rows. Row 0 is is indicated as firstline=0, lastline=1. Row 14 would be firstline=14, lastline=15. I see good results from echo. I want to do the same with script #1. Can't get my head around nesting correctly. Code below.
#!/bin/bash
# script 1
filename=slash
firstline=0
lastline=1
i=0
exec <${filename}
while read ; do
i=$(( $i + 1 ))
if [ "$i" -ge "${firstline}" ] ; then
if [ "$i" -gt "${lastline}" ] ; then
break
else
echo "${REPLY}" > slash1
fold -w 21 -s slash1 > news1
sleep 5
fi
fi
done
# script2
firstline=(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14)
lastline=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)
for ((i=0;i<${#firstline[#]};i++))
do
echo ${firstline[$i]} ${lastline[$i]};
done
Your question is very unclear, but perhaps you are simply looking for some simple function calls:
#!/bin/bash
script_1() {
filename=slash
firstline=$1
lastline=$2
i=0
exec <${filename}
while read ; do
i=$(( $i + 1 ))
if [ "$i" -ge "${firstline}" ] ; then
if [ "$i" -gt "${lastline}" ] ; then
break
else
echo "${REPLY}" > slash1
fold -w 21 -s slash1 > news1
sleep 5
fi
fi
done
}
# script2
firstline=(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14)
lastline=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)
for ((i=0;i<${#firstline[#]};i++))
do
script_1 ${firstline[$i]} ${lastline[$i]};
done
Note that reading the file this way is extremely inefficient, and there are undoubtedly better ways to handle this, but I am trying to minimize the changes from your code.
Update: Based on your later comments, the following idiomatic Bash code that uses sed to extract the line of interest in each iteration solves your problem much more simply:
Note:
- If the input file does not change between loop iterations, and the input file is small enough (as it is in the case at hand), it's more efficient to buffer the file contents in a variable up front, as is demonstrated in the original answer below.
- As tripleee points out in a comment: If simply reading the input lines sequentially is sufficient (as opposed to extracting lines by specific line numbers, then a single, simple while read -r line; do ... # fold and output, then sleep ... done < "$filename" is enough.
# Determine the input filename.
filename='slash'
# Count its number of lines.
lineCount=$(wc -l < "$filename")
# Loop over the line numbers of the file.
for (( lineNum = 1; lineNum <= lineCount; ++lineNum )); do
# Use `sed` to extract the line with the line number at hand,
# reformat it, and output to the target file.
fold -w 21 -s <(sed -n "$lineNum {p;q;}" "$filename") > 'news1'
sleep 5
done
A simplified version of what I think you're trying to achieve:
#!/bin/bash
# Split fields by newlines on input,
# and separate array items by newlines on output.
IFS=$'\n'
# Read all input lines up front, into array ${lines[#]}
# In terms of your code, you'd use
# read -d '' -ra lines < "$filename"
read -d '' -ra lines <<<$'line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\nline 9\nline 10\nline 11\nline 12\nline 13\nline 14\nline 15'
# Define the arrays specifying the line ranges to select.
firstline=(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14)
lastline=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)
# Loop over the ranges and select a range of lines in each iteration.
for ((i=0; i<${#firstline[#]}; i++)); do
extractedLines="${lines[*]: ${firstline[i]}: 1 + ${lastline[i]} - ${firstline[i]}}"
# Process the extracted lines.
# In terms of your code, the `> slash1` and `fold ...` commands would go here.
echo "$extractedLines"
echo '------'
done
Note:
The name of the array variable filled with read -ra is lines; ${lines[#]} is Bash syntax for returning all array elements as separate words (${lines[*]} also refers to all elements, but with slightly different semantics), and this syntax is used in the comments to illustrate that lines is indeed an array variable (note that if you were to use simply $lines to reference the variable, you'd implicitly get only the item with index 0, which is the same as: ${lines[0]}.
<<<$'line 1\n...' uses a here-string (<<<) to read an ad-hoc sample document (expressed as an ANSI C-quoted string ($'...')) in the interest of making my example code self-contained.
As stated in the comment, you'd read from $filename instead:
read -d '' -ra lines <"$filename"
extractedLines="${lines[*]: ${firstline[i]}: 1 + ${lastline[i]} - ${firstline[i]}}" extracts the lines of interest; ${firstline[i]} references the current element (index i) from array ${firstline[#]}; since the last token in Bash's array-slicing syntax
(${lines[*]: <startIndex>: <elementCount>}) is the count of elements to return, we must perform a calculation to determine the count, which is what 1 + ${lastline[i]} - ${firstline[i]} does.
By virtue of using "${lines[*]...}" rather than "${lines[#]...}", the extracted array elements are joined by the first character in $IFS, which in our case is a newline ($'\n') (when extracting a single line, that doesn't really matter).

split file into several sub files

The file I am working on looks like this
header
//
[25]:0.00843832,469:0.0109533):0.00657864,((((872:0.00120503,((980:0.0001);
[29]:((962:0.000580339,930:0.000580339):0.00543993);
absolute:
gthcont: 5 4 2 1 3 4 543 5 67 657 78 67 8 5645 6
01010010101010101010101010101011111100011
1111010010010101010101010111101000100000
00000000000000011001100101010010101011111
I need it to be split into four files. The first file is
[25]:0.00843832,469:0.0109533):0.00657864,((((872:0.00120503,((980:0.0001);
[29]:((962:0.000580339,930:0.000580339):0.00543993);
The second file has to be
5 4 2 1 3 4 543 5 67 657 78 67 8 5645 6
The next file has to be
01010010101010101010101010101011111100011
11110100100101010101010101111010001000001
00000000000000011001100101010010101011111
so the header and the // have to be excluded before the first file, the absolute: line should be removed and the gthcont: shoudl not pop up as well.
Ideally the script would just take the input name of the file and name the output as first_input, second_input and third_input...
the fourth file should have the numbers from within the brackets in the first file..in this case it woudl only be
25
29
so my current try ist
awk.awk
BEGIN{body=0}
!body && /^\/\/$/ {body=1}
body && /^\[/ {print > "first_"FILENAME}
body && /^pos/{$1="";print > "second_"FILENAME}
body && /^[01]+/ {print > "third_"FILENAME}
body && /^\[[0-9]+\]/ {
print > "first_"FILENAME
print substr($0, 2, index($0,"]")-2) > "fourth_"FILENAME
}
but is somehow duplicates the lines in the first file so it would be [25], [25], [29],[29]
Some very minor changes to your script produce the desired output:
!body && /^\/\/$/ {body=1}
body && sub(/^gthcont: */,"") {print > "second_"FILENAME}
body && /^[01]+/ {print > "third_"FILENAME}
body && /^\[[0-9]+\]/ {
print > "first_"FILENAME
print substr($0, 2, index($0,"]")-2) > "fourth_"FILENAME
}
The duplication problem was caused by the fact that you printed to the first file in two places.
I have used sub to remove the first part of the gthcont: line (and changed the pattern too). sub returns true if it makes any replacements, so you can use it as a test as well. The advantage of using a substitution rather than unsetting the first field is that you can also get rid of the leading white space from the line.
As pointed out in the comments, there is no need to initialise body, so I removed the BEGIN block too.
I would just use a shell function for this:
function split3 {
if [[ $# -ne 1 ]]; then echo 'split3: error: require 1 argument.' >&2; return 1; fi;
while read -r; do
line=$REPLY;
if [[ "$line" =~ ^\[([0-9]+)\]: ]]; then
echo "$line" >&3;
echo "${BASH_REMATCH[1]}" >&6;
elif [[ "$line" =~ ^gthcont: ]]; then
echo "${line#gthcont: }" >&4;
elif [[ "$line" =~ ^\s*[01]+\s*$ ]]; then
echo "$line" >&5;
fi;
done <"$1" 3>"first_$1" 4>"second_$1" 5>"third_$1" 6>"fourth_$1";
};
split3 input; echo $?;
## 0
cat first_input;
## [25]:0.00843832,469:0.0109533):0.00657864,((((872:0.00120503,((980:0.0001);
## [29]:((962:0.000580339,930:0.000580339):0.00543993);
cat second_input;
## 5 4 2 1 3 4 543 5 67 657 78 67 8 5645 6
cat third_input;
## 01010010101010101010101010101011111100011
## 1111010010010101010101010111101000100000
## 00000000000000011001100101010010101011111
cat fourth_input;
## 25
## 29

bash script: for loop breaks on the first iteration without explanation

I am trying to create a script in order to break down a file into 24. The "infoband.dat" contains the data of 24 bands that i want to plot, but rather than writing each band separately, it first writes all the 1st points of each band, then all the 2nd points, etc.
My script was supposed to begin reading each line of the file, while count to 24 over and over until the end of the file. On the first iteration of the for loop, it would create a file with all the first lines out of those 24 line chunks, and it does it successfully. But the second iteration doesn't even start. What is breaking the loop?
1 #!/bin/sh
2 grep frequency band.yaml > infoband.dat
3 contadora=0
4 for i in {1..24} #loop to create the band file, 24 is the no. of bands
5 do
6 contadora=$((contadora+1))
7 contadorb=0
8 contadorc=0
9 while read line
10 do
11 contadorb=$((contadorb+1))
12 if [ $contadorb -eq 25 ]
13 then
14 contadorb=1
15 contadorc=$((contadorc+1))
16 fi
19 if [ $contadora -eq $contadorb ]
20 then
21 echo $contadora $contadorb $contadorc "$line" >> band_$contadora.dat
22 fi
23 done < infoband.dat
24 echo "file of the band " $contadora "is finished"
25 done
Update: i got the code done using a different approach (the variable contadorc is useless btw):
1 #!/bin/sh
2 grep frequency band.yaml > infoband.dat
3 nband=24
4 contadorb=0
5 contadorc=0
6 while read line
7 do
8 contadorb=$((contadorb+1))
9 if [ $contadorb -eq $((nband+1)) ]
10 then
11 contadorb=1
12 contadorc=$((contadorc+1))
13 fi
14 echo " "$contadorb" "$line" punto_q $contadorc">> test_infoband.dat
15 done < infoband.dat
16 for i in `seq 1 $nband`
17 do
18 echo $i $nband
19 grep " $i " test_infoband.dat > banda_$i.dat
20 done
/bin/sh doesn't do brace expansion, so your loop only has one iteration, in which i is set to the string {1..24}. Either change the hashtag to /bin/bash and/or run the script with bash, or use
for i in $(seq 1 24)
(assuming your system has the seq command, otherwise you may need to just hard-code the list, or use a while loop to explicitly increment and test the value of i).
Did you try using the command "split"?
split -l 24 infoband.dat

Resources