I am quite new to bash scripting, but haven't found an answer to the following problem yet. I hope somebody can either tell me or give me tips on how to do it.
Background: I have a program (say "program") that accepts an ini-file (say "input.ini") as input taking a while to execute. A variable in the ini-file for the programm might be "number" for instance, which might be set to number=1.
Problem: I have to call ./program input.ini quite often, but with different values for "number", say 1,2,3,4,5.
I thought, I could write a bash script executing the program in a for-loop setting "number" accordingly. The loop is not a problem, but setting "number" in the ini-file. (I tried e.g. number=$VALUE in the ini-file with VALUE being set in the script, but this does not work.)
Question: How can I set a variable in the ini-file using a bash-script? (This does not have to be permanent, but only for that run of the program.)
Additional question: Setting a variable in the ini-file is one thing. In addition, it would be great to do the following as well (I thought that might work similarly...): The program produces some output files. The names of these files can also be set in the ini-file, say via "output_name=filename.out". It would be great now if there was something like "output_name=filename_$VALUE.out" to set the output names accordingly.
I hope it is clear what I try to do and I would be really grateful if somebody had a solution or hints on how to do it.
Thanks,
Cari
If you have a file that contains number=something, you can replace "something" with "5" using sed "/^number=/s/=.*/=5/.
This is something you can do once off with process substituion:
./program <(sed "/^number=/s/=.*/=5/" baseinput.ini)
Or you can create a new ini file based on the old one, as in
sed "/^number=/s/=.*/=5/" baseinput.ini > input.ini
./program input.ini
You could also define the entire ini file the script, and substitute in a here document:
N=5
./program - << EOF
[Section]
number=$N
foo=bar
EOF
full parsed and set ini file (section,key,value) and save on root.
sudo_setini ()
{
fkey=false
fsec=false
tsec=false
res=""
if [ -f "$1" ]; then
while IFS= read -r LINE
do
TLINE=`echo $LINE`
if [[ $TLINE == \[*] ]]; then
TLINE=`echo ${TLINE:1:${#TLINE}-2}`
if [[ _${TLINE} == _$2 ]]; then
tsec=true
fsec=true
else
if $tsec && ! $fkey ; then
res+=$'\n'$3=$4
fi
tsec=false
fi
res+=$'\n'${LINE}
else
TLINE=`echo ${TLINE%%=*}`
if $tsec && [[ _${TLINE} == _$3 ]]; then
fkey=true
res+=$'\n'${LINE%%=*}=$4
else
res+=$'\n'${LINE}
fi
fi
done < $1
fi
if $tsec && ! $fkey ; then
res+=$'\n'$3=$4
fi
if ! $fsec ; then
res+=$'\n'[$2]
res+=$'\n'$3=$4
fi
echo "$res" | sudo tee "$1" > /dev/null
}
sudo_setini 'test.ini' 'General' 'Type' 'Digital_'
Not quite sure whether this helps or not:
This calls the program script five times:
for n in 1 2 3 4 5
do
./program $n input.ini
done
Then in program, refer to the first parameter $n as $1.
The second parameter input.ini is $2.
If you have git available and you're not worried about indentation, a hack could be to use git config.
Example:
$ git config -f settings.ini server.ip 123.123.123.123
$ cat settings.ini
[server]
ip = 123.123.123.123
$ git config -f settings.ini server.ip 123.123.123.124
$ cat settings.ini
[server]
ip = 123.123.123.124
Related
This question already has an answer here:
Script fails with spaces in directory names
(1 answer)
Closed 6 years ago.
I found this script online that I'm trying to edit, but as I'm testing it I can see that it will spit out a bunch of errors for all the files I have with spaces. This is the kind of error log I get on the terminal window:
Skipping 1-03 as ./mp3/basename "$input_file" .wav.mp3 exists.
Skipping The as ./mp3/basename "$input_file" .wav.mp3 exists.
Skipping power, as ./mp3/basename "$input_file" .wav.mp3 exists.
And this is the script:
#!/bin/bash
# Title: wav_to_mp3.sh
# Purpose: Converts all WAV files present in the folder to MP3
# Author: Karthic Raghupathi, IVR Technology Group LLC
# Last Revised: 2014.01.28
# references
sox="/usr/local/bin/sox"
sox_options="-S"
# variables
source_folder="${1:-.}"
destination_folder="${source_folder}/mp3"
environment="${2:-DEVELOPMENT}"
# check to see if an environment flag was supplied
if [ $environment = "PRODUCTION" ] || [ $environment = "production" ]; then
sox="/usr/bin/sox"
environment="PRODUCTION"
fi
# print all params so user can see
clear
echo "Script operating using the following settings and parameters....."
echo ""
echo "which SoX: ${sox}"
echo "SoX options: ${sox_options}"
echo "Environment: ${environment}"
echo "Source: ${source_folder}"
echo "Destination: ${destination_folder}"
echo ""
read -e -p "Do you wish to proceed? (y/n) : " confirm
if [ $confirm = "N" ] || [ $confirm = "n" ]; then
exit
fi
# create destination if it does not exist
if [ ! -d "${destination_folder}" ]; then
mkdir -p "${destination_folder}"
fi
# loop through all files in folder and convert them to
for input_file in $(ls -1 $1 | grep .wav)
do
name_part=`basename "$input_file" .wav`
output_file="$name_part.mp3"
# create mp3 if file does not exist
if [ ! -f "$destination_folder/$output_file" ]; then
$sox $sox_options "${source_folder}/$input_file" "$destination_folder/$output_file"
else
echo "Skipping ${input_file} as $destination_folder/$output_file exists."
fi
done
I know I'm supposed to make it escape the space characters, but I can't figure out how. I tried changing some quotes here and there but I'm just breaking it.
BTW, if anyone would be so kind as to link a good tutorial for learning how to make bash scripts on Mac OS (or Unix), that would be much appreciated. I already know a bit of web programming so I'm not a complete n00b, but still, I'm having trouble creating very simple scripts and I would like to learn independently without constantly bugging the internet for help :)
This is wrong:
for input_file in $(ls -1 $1 | grep .wav)
See here why. Also, inside $1, try this to see that filenames with spaces give trouble:
for i in $(ls -1 | grep wav); do echo $i; done
Try this instead:
for input_file in $1/*.wav
You can escape spaces by inserting a backslash character before the space.
Change:
This file name
To:
This\ file\ name
It might be an idea to write a function to do this for you, iterate through each character in a string and adding a \ caracter before any spaces. that way you don't need to worry about pre-formatting the file names and escaping each individual space - just run the file name through the function and capture the result.
I am trying to write a bash script that will do the following:
Take a directory or file as input (will always begin with /mnt/user/)
Search other mount points for same file or directory (will always begin with /mnt/diskx)
Return value
So, for example, the input will be "/mnt/user/my_files/file.txt". It will search if ""/mnt/disk1/my_files/file.txt" exists and will incrementally look for each disk (disk2, disk3, etc) until it finds it or disk20.
This is what I have so far:
#/user/bin/bash
var=$1
i=0
while [ -e $check_var = echo $var | sed 's:/mnt/user:/mnt/disk$i+1:']
do
final=$check_var
done
It's incomplete yes, but I am not that proficient in bash so I'm doing a little at a time. I'm sure my command won't work properly yet either but right now I am getting an "unexpected end of file" and I can't figure out why.
There are many issues here:
If this is the actual code you're getting "unexpected end of file" on, you should save the file in Unix format, not DOS format.
The shebang should be #!/usr/bin/bash or #!/bin/bash depending on your system
You have to assign check_var before running [ .. ] on it.
You have to use $(..) to expand a command
Variables like $i are not expanded in single quotes
sed can't add numbers
i is never incremented
the loop logic is inverted, it should loop until it matches and not while it matches.
You'd want to assign final after -- not in -- the loop.
Consider doing it in even smaller pieces, it's easier to debug e.g. the single statement sed 's:/mnt/user:/mnt/disk$i+1:' than your entire while loop.
Here's a more canonical way of doing it:
#!/bin/bash
var="${1#/mnt/user/}"
for file in /mnt/disk{1..20}/"$var"
do
[[ -e "$file" ]] && final="$file" && break
done
if [[ $final ]]
then
echo "It exists at $final"
else
echo "It doesn't exist anywhere"
fi
I'm trying to run the following code on files that I choose and put into a variable source file. This is for ease of the user when I export it out. This is the code before trying to add a source file:
for file in ~/Twitter/Users/New/*; do
[ -f "$file" ] && sed '1,7d' "$file" | head -n -9 > ~/Twitter/Users/TBA/"${file##*/}"
done
So I tried adding a source file like so:
#!/bin/bash
source ~/MYBASHSCRIPTS/Tests/scriptsettings.in
for file in $loctorem/*; do
[ -f "$file" ] && sed '1,7d' "$file" | head -n -9 > $locdone
done
echo $loctorem
echo $locdone
with the scriptsettings.in configured as such:
loctorem="~/Twitter/Users/New"
locdone='~/Twitter/Users/TBA/"${file##*/}"'
I have tried both half old/half new code but neither work. does it really need to be hard coded in order to run? This will throw my whole "noob friendly" idea in the trash if so...
EDIT--- I only echo it at the end so that I can verify that it is calling the correct locations.
EDIT2--- Here is the exact script I original ran.
#!/bin/bash
for file in ~/Anon/Twitter/OpISIS/New/*; do
[ -f "$file" ] && sed '1,7d' "$file" | head -n -9 > ~/Anon/Twitter/OpISIS/TBA/"${file##*/}"
done
And the new variant:
source ~/MYBASHSCRIPTS/Tests/scriptsettings.in
for file in $loctorem/*; do
[ -f "$file" ] && sed '1,7d' "$file" | head -n -9 > "$(locdone_path "$file")"
done
with the source file being:
loctorem=/home/matrix/Anon/Twitter/OpISIS/New
locdone_path() { printf '%s\n' ~/Twitter/Users/TBA/"${1##*/}
as I said before, I'm still pretty new so sorry ifI'm doing an insanely stupid thing in here..
I'm trying to make the input and output folder/file set to a variable that the user can change. in the end this script will be ~80 lines and I want anyone to be able to run it instead of forcing everyone to have directories/files set up like mine. Then I'll have a setup script that makes the file with the variables stored in them so there is a one time setup, or the user can later change locations but they dont have to go into the entire code and change everything just to fit their system.
You've got two problems here. The first are the quotes, which prevent tilde expansion:
# this stores a path with a literal ~ character
loctorem='~/Twitter/Users/New'
# this works
loctorem=~/Twitter/Users/New
# this works too
loctorem="$HOME/Twitter/Users/New"
The second issue is that you're depending on $file before it's available. If you want to store code (an algorithm on how to calculate something, for instance), in your configuration, define a function:
# this can be put in your sourced file
locdone_path() { printf '%s\n' ~/Twitter/Users/TBA/"${1##*/}"; }
...and, later, to use that code, invoke the function:
... | head -n 9 >"$(locdone_path "$file")"
However, if you only want to make the directory customizable, you might do something much simpler:
loctorem=~/Twitter/Users/New
locdone=~/Twitter/Users/TBA
and:
... | head -n 9 >"$locdone/${file##*/}"
so I am trying to make a bash script loop that takes a users file name they want and the number of files they want and creates empty files. I made the script but I keep getting the error "./dog.sh: line 6: 0]: No such file or directory". I'm new to bash script and don't know what I'm doing wrong. Any help would be awesome thanks!
#!/bin/bash
echo "what file name do you want?"; read filename
echo "how many files do you want"; read filenumber
x=$filenumber
if [$x < 0]
then
touch $fiename$filenumber
x=$((x--))
fi
for x in $(seq "$filenumber"); do touch "$filename$x"; done
seq $filenumber produces a list of numbers from 1 to $filenumber. The for loop assigns x to each of these numbers in turn. The touch command is run for each value of x.
Alternative
In bash, if we can type the correct file number into the command line, the same thing can be accomplished without a for loop:
$ touch myfile{1..7}
$ ls
myfile1 myfile2 myfile3 myfile4 myfile5 myfile6 myfile7
{1..7} is called "brace expansion". Bash will expand the expression myfile{1..7} to the list of seven files that we want.
Brace expansion does have a limitation. It does not support shell variables. So, touch myfile{1..$filenumber} would not work. We have to enter the correct number in the braces directly.
Maybe it's a typo: $fiename instead of $filename
also, you might want some kind of loop like so:
x=1
while [ $x -le $filenumber ]; do
touch $filename$x
let x=x+1
done
#!/bin/bash
echo "what file name do you want?"; read filename
echo "how many files do you want"; read filenumber
x=$filenumber
while [ $x -gt 0 ]; do
touch $filename$x
x=$(( $x - 1))
done
I have written a small bash script called "isinFile.sh" for checking if the first term given to the script can be found in the file "file.txt":
#!/bin/bash
FILE="file.txt"
if [ `grep -w "$1" $FILE` ]; then
echo "true"
else
echo "false"
fi
However, running the script like
> ./isinFile.sh -x
breaks the script, since -x is interpreted by grep as an option.
So I improved my script
#!/bin/bash
FILE="file.txt"
if [ `grep -w -- "$1" $FILE` ]; then
echo "true"
else
echo "false"
fi
using -- as an argument to grep. Now running
> ./isinFile.sh -x
false
works. But is using -- the correct and only way to prevent code/option injection in bash scripts? I have not seen it in the wild, only found it mentioned in ABASH: Finding Bugs in Bash Scripts.
grep -w -- ...
prevents that interpretation in what follows --
EDIT
(I did not read the last part sorry). Yes, it is the only way. The other way is to avoid it as first part of the search; e.g. ".{0}-x" works too but it is odd., so e.g.
grep -w ".{0}$1" ...
should work too.
There's actually another code injection (or whatever you want to call it) bug in this script: it simply hands the output of grep to the [ (aka test) command, and assumes that'll return true if it's not empty. But if the output is more than one "word" long, [ will treat it as an expression and try to evaluate it. For example, suppose the file contains the line 0 -eq 2 and you search for "0" -- [ will decide that 0 is not equal to 2, and the script will print false despite the fact that it found a match.
The best way to fix this is to use Ignacio Vazquez-Abrams' suggestion (as clarified by Dennis Williamson) -- this completely avoids the parsing problem, and is also faster (since -q makes grep stop searching at the first match). If that option weren't available, another method would be to protect the output with double-quotes: if [ "$(grep -w -- "$1" "$FILE")" ]; then (note that I also used $() instead of backquotes 'cause I find them much easier to read, and quotes around $FILE just in case it contains anything funny, like whitespace).
Though not applicable in this particular case, another technique can be used to prevent filenames that start with hyphens from being interpreted as options:
rm ./-x
or
rm /path/to/-x