Read file in bash script with loop - linux

Given file socat.conf
AUTOSTART=default
SOCAT_default="TCP4-LISTEN:3724,nodelay,fork,reuseaddr,su=nobody TCP4:your.wow.server.ip.address:3724,nodelay"
The relevant part of the bash script that reads this file:
[ ! -f /etc/default/socat.conf ] || . /etc/default/socat.conf
start () {
echo "Starting $DESC:"
maxfds
umask 027
cd /tmp
if test "x$AUTOSTART" = "xnone" -o -z "x$AUTOSTART" ; then
echo "Autostart disabled."
exit 0
fi
for NAME in $AUTOSTART ; do
ARGS=`eval echo \\\$SOCAT_$NAME`
echo $ARGS
start_socat
echo " $NAME $ARGS"
done
return $?
}
For the full file see here: https://blog.bentrax.de/2009/08/26/socat-start-automatisieren-und-iptables-regeln-laden/
My question is, how can I add another command to socat.conf? I tried with
AUTOSTART=default,another
SOCAT_default="TCP4-LISTEN:3724,nodelay,fork,reuseaddr,su=nobody TCP4:your.wow.server.ip.address:3724,nodelay"
SOCAT_another="..."
However this did not work. I am not very familiar with bash scripts to understand the for NAME in $AUTOSTART loop. I think the answer lays there. Any ideas?

The for NAME in $AUTOSTART works by splitting $AUTOSTART into words using the environmental variable $IFS as delimiters (default is space, tab and newline). Each word in turn is then stored in $NAME and processed within the loop until no words remain.
The solution to your problem, then, is to separate your words using spaces (or tabs, or newlines..):
AUTOSTART="default another"
The double quotes are necessary, otherwise it will be read as two separate commands, AUTOSTART=default and another (again because of word-splitting using IFS).

Related

how to pass asterisk into ls command inside bash script

Hi… Need a little help here…
I tried to emulate the DOS' dir command in Linux using bash script. Basically it's just a wrapped ls command with some parameters plus summary info. Here's the script:
#!/bin/bash
# default to current folder
if [ -z "$1" ]; then var=.;
else var="$1"; fi
# check file existence
if [ -a "$var" ]; then
# list contents with color, folder first
CMD="ls -lgG $var --color --group-directories-first"; $CMD;
# sum all files size
size=$(ls -lgGp "$var" | grep -v / | awk '{ sum += $3 }; END { print sum }')
if [ "$size" == "" ]; then size="0"; fi
# create summary
if [ -d "$var" ]; then
folder=$(find $var/* -maxdepth 0 -type d | wc -l)
file=$(find $var/* -maxdepth 0 -type f | wc -l)
echo "Found: $folder folders "
echo " $file files $size bytes"
fi
# error message
else
echo "dir: Error \"$var\": No such file or directory"
fi
The problem is when the argument contains an asterisk (*), the ls within the script acts differently compare to the direct ls command given at the prompt. Instead of return the whole files list, the script only returns the first file. See the video below to see the comparation in action. I don't know why it behaves like that.
Anyone knows how to fix it? Thank you.
Video: problem in action
UPDATE:
The problem has been solved. Thank you all for the answers. Now my script works as expected. See the video here: http://i.giphy.com/3o8dp1YLz4fIyCbOAU.gif
The asterisk * is expanded by the shell when it parses the command line. In other words, your script doesn't get a parameter containing an asterisk, it gets a list of files as arguments. Your script only works with $1, the first argument. It should work with "$#" instead.
This is because when you retrieve $1 you assume the shell does NOT expand *.
In fact, when * (or other glob) matches, it is expanded, and broken into segments by $IFS, and then passed as $1, $2, etc.
You're lucky if you simply retrieved the first file. When your first file's path contains spaces, you'll get an error because you only get the first segment before the space.
Seriously, read this and especially this. Really.
And please don't do things like
CMD=whatever you get from user input; $CMD;
You are begging for trouble. Don't execute arbitrary string from the user.
Both above answers already answered your question. So, i'm going a bit more verbose.
In your terminal is running the bash interpreter (probably). This is the program which parses your input line(s) and doing "things" based on your input.
When you enter some line the bash start doing the following workflow:
parsing and lexical analysis
expansion
brace expansion
tidle expansion
variable expansion
artithmetic and other substitutions
command substitution
word splitting
filename generation (globbing)
removing quotes
Only after all above the bash
will execute some external commands, like ls or dir.sh... etc.,
or will do so some "internal" actions for the known keywords and builtins like echo, for, if etc...
As you can see, the second last is the filename generation (globbing). So, in your case - if the test* matches some files, your bash expands the willcard characters (aka does the globbing).
So,
when you enter dir.sh test*,
and the test* matches some files
the bash does the expansion first
and after will execute the command dir.sh with already expanded filenames
e.g. the script get executed (in your case) as: dir.sh test.pas test.swift
BTW, it acts exactly with the same way for your ls example:
the bash expands the ls test* to ls test.pas test.swift
then executes the ls with the above two arguments
and the ls will print the result for the got two arguments.
with other words, the ls don't even see the test* argument - if it is possible - the bash expands the wilcard characters. (* and ?).
Now back to your script: add after the shebang the following line:
echo "the $0 got this arguments: $#"
and you will immediatelly see, the real argumemts how your script got executed.
also, in such cases is a good practice trying to execute the script in debug-mode, e.g.
bash -x dir.sh test*
and you will see, what the script does exactly.
Also, you can do the same for your current interpreter, e.g. just enter into the terminal
set -x
and try run the dir.sh test* = and you will see, how the bash will execute the dir.sh command. (to stop the debug mode, just enter set +x)
Everbody is giving you valuable advice which you should definitely should follow!
But here is the real answer to your question.
To pass unexpanded arguments to any executable you need to single quote them:
./your_script '*'
The best solution I have is to use the eval command, in this way:
#!/bin/bash
cmd="some command \"with_quetes_and_asterisk_in_it*\""
echo "$cmd"
eval $cmd
The eval command takes its arguments and evaluates them into the command as the shell does.
This solves my problem when I need to call a command with asterisk '*' in it from a script.

Delete words from given files with sed

I have this assignment to solve:
"Write a shell script that continuously reads words from the keyboard and
deletes them from all the files given in the command line."
I've tried to solve it, here's my attempt:
#!/bin/bash
echo "Enter words"
while (true)
do
read wrd
if [ "$wrd" != "exit" ]
then
for i in $#
do
sed -i -e 's/$wrd//g' $i
done
else
break
fi
done
This is the error that I receive after introducing the command: ./h84a.sh fisier1.txt
Enter words
suc
sed: can't read 1: No such file or directory
Sorry if I'm not very specific, it's my first time posting in here. I'm working in a terminal on Linux Mint which is installed on another partition of my PC. Please help me with my problem. Thanks!
I think you can simplify your script quite a lot:
#!/bin/bash
echo "Enter words"
while read -r wrd
do
[ "$wrd" = exit ] && break
sed -i "s/$wrd//g" "$#"
done
Some key changes:
The double quotes around the sed command are essential, as shell variables are not expanded within single quotes
Instead of using a loop, it is possible to pass all of the file names to sed at once, using "$#"
read -r is almost always what you want to use
I would suggest that you take care with in-place editing using the -i switch. In some versions of sed, you can specify the suffix of a backup file like -i.bak, so the original file is not lost.
In case you're not familiar with the syntax [ "$wrd" = exit ] && break, it is functionally equivalent to:
if [ "$wrd" = exit ]
then break
fi
$# expands to the number of arguments (so 1 in this case)
You probably meant to use $* or "$#"

prevent the terminal from closing when the custom bash function is run

I wrote the following program in my linux bashrc
open()
{
echo enter file name
read fname
locate $fname> /home/vvajendla/Desktop/backup/loc;
cat loc
exec < /home/vvajendla/Desktop/backup/loc;
value=0
while read line
do
value=`expr $value + 1`;
echo $value
echo $line
if [ $value -le 6 ]
then
gedit $line;
else
echo too many files to open
fi
done
}
The above function searches all the directories for the file-string match and opens them using GEDIT if they are less than or equal to 6.
whenever i run this function in the terminal,it gets closed.
Can you please tell me what i can do to keep it open?
The exec causes the standard input of the calling shell to be permanently redirected from the file. Once the file closes, the shell runs out of input, and exits. I assume you import this function with source; running it standalone should work.
The usual way to write this sort of function would be to make it accept an argument, so you would invoke it like "open fnord" instead of run "open" and enter "fnord" at the prompt.
open () {
local fname
fname=$1 # notice this arrangement instead of read
local value
value=0
locate "$fname" | # notice double quotes
tee /dev/stderr | # as a superior alternative to using a temporary file
while read line
do
value=`expr $value + 1`
if [ $value -le 6 ]
then
gedit "$line" # notice double quotes
else
echo too many files to open >&2 # notice redirection to stderr
fi
done
}
The diagnostic is misleading; this code will still open the first six files, then bail with an error message at the seventh. Is that what you intend? Or should it count the number of outputs, and refuse to run if there are more than six?
If you don't care for the other improvements, the minimal fix is to remove the exec and read the while loop's input from your temporary file. (You should take care to properly clean up; if you can avoid a temporary file, that's basically always a better solution.)
while read line; do
....
done <tempfile
I would be tempted to add line numbers with nl to get rid of the unattractive expr, but this might break file names with a space at the beginning. (On the other hand, locate always produces a full path name, right?)
As an alternative, and assuming gedit can read multiple file name arguments, try this:
locate "$fname" | head -n 6 | xargs gedit
This fails to produce a warning if there are more than six files, but I would actually consider that a feature.

Shell - Write variable contents to a file

I would like to copy the contents of a variable (here called var) into a file.
The name of the file is stored in another variable destfile.
I'm having problems doing this. Here's what I've tried:
cp $var $destfile
I've also tried the same thing with the dd command... Obviously the shell thought that $var was referring to a directory and so told me that the directory could not be found.
How do I get around this?
Use the echo command:
var="text to append";
destdir=/some/directory/path/filename
if [ -f "$destdir" ]
then
echo "$var" > "$destdir"
fi
The if tests that $destdir represents a file.
The > appends the text after truncating the file. If you only want to append the text in $var to the file existing contents, then use >> instead:
echo "$var" >> "$destdir"
The cp command is used for copying files (to files), not for writing text to a file.
echo has the problem that if var contains something like -e, it will be interpreted as a flag. Another option is printf, but printf "$var" > "$destdir" will expand any escaped characters in the variable, so if the variable contains backslashes the file contents won't match. However, because printf only interprets backslashes as escapes in the format string, you can use the %s format specifier to store the exact variable contents to the destination file:
printf "%s" "$var" > "$destdir"
None of the answers above work if your variable:
starts with -e
starts with -n
starts with -E
contains a \ followed by an n
should not have an extra newline appended after it
and so they cannot be relied upon for arbitrary string contents.
In bash, you can use "here strings" as:
cat <<< "$var" > "$destdir"
As noted in the comment by Ash below, #Trebawa's answer (formulated in the same room as mine!) using printf is a better approach than cat.
All of the above work, but also have to work around a problem (escapes and special characters) that doesn't need to occur in the first place: Special characters when the variable is expanded by the shell. Just don't do that (variable expansion) in the first place. Use the variable directly, without expansion.
Also, if your variable contains a secret and you want to copy that secret into a file, you might want to not have expansion in the command line as tracing/command echo of the shell commands might reveal the secret. Means, all answers which use $var in the command line may have a potential security risk by exposing the variable contents to tracing and logging of the shell.
For variables that are already exported, use this:
printenv var >file
That means, in case of the OP question:
printenv var >"$destfile"
Note: variable names are case sensitive.
Warning: It is not a good idea to export a variable just for the sake of printing it with printenv. If you have a non-exported script variable that contains a secret, exporting it will expose it to all future child processes (unless unexported, for example using export -n).
If I understood you right, you want to copy $var in a file (if it's a string).
echo $var > $destdir
When you say "copy the contents of a variable", does that variable contain a file name, or does it contain a name of a file?
I'm assuming by your question that $var contains the contents you want to copy into the file:
$ echo "$var" > "$destdir"
This will echo the value of $var into a file called $destdir. Note the quotes. Very important to have "$var" enclosed in quotes. Also for "$destdir" if there's a space in the name. To append it:
$ echo "$var" >> "$destdir"
you may need to edit a conf file in a build process:
echo "db-url-host=$POSTGRESQL_HOST" >> my-service.conf
You can test this solution with running before export POSTGRESQL_HOST="localhost"

Search log file for string with bash script

I just started learning PHP. I'm following phpacademy's tutorials which I would recommend to anyone. Anyways, I'm using XAMPP to test out my scripts. I'm trying to write a bash script that will start XAMPP and then open firefox to the localhost page if it finds a specific string, "XAMPP for Linux started.", that has been redirected from the terminal to the file xampp.log. I'm having a problem searching the file. I keep getting a:
grep: for: No such file or directory
I know the file exists, I think my syntax is wrong. This is what I've got so far:
loaded=$false
string="XAMPP for Linux started."
echo "Starting Xampp..."
sudo /opt/lampp/lampp start 2>&1 > ~/Documents/xampp.log
sleep 15
if grep -q $string ~/Documents/xampp.log; then
$loaded=$true
echo -e "\nXampp successfully started!"
fi
if [$loaded -eq $true]; then
echo -e "Opening localhost..."
firefox "http://localhost/"
else
echo -e "\nXampp failed to start."
echo -e "\nHere's what went wrong:\n"
cat ~/Documents/xampp.log
fi
In shell scripts you shouldn't write $variable, since that will do word expansion on the variable's value. In your case, it results in four words.
Always use quotes around the variables, like this:
grep -e "$string" file...
The -e is necessary when the string might start with a dash, and the quotes around the string keep it as one word.
By the way: when you write shell programs, the first line should be set -eu. This enables *e*rror checking and checks for *u*ndefined variables, which will be useful in your case. For more details, read the Bash manual.
You are searching for a string you should put wihtin quotes.
Try "$string" instead of $string
There are a couple of problems:
quote variables if you want to pass them as a simple argument "$string"
there is no $true and $false
bash does variable expansion, it substitutes variable names with their values before executing the command. $loaded=$true should be loaded=true.
you need spaces and usually quotes in the if: if [$loaded -eq $true] if [ "$loaded" -eq true ]. in this case the variable is set so it won't cause problems but in general don't rely on that.

Resources