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 "$#"
Related
I am trying to pass a user-input variable (a file name) into a foreach loop in tcsh. The user entered variable is, for example, "files.list" (saved in the same folder as the Shell Script is saved, and is being run from).
Here is my code:
#! /usr/bin/tcsh -f
echo please enter files list
set x = $<
foreach i ('$x')
echo $i
end
What I want is for each of the words in "files.list" to be output to the screen. Files.list contains 5 lines, each with a file name.
myScript22.sh
Mad45.sh
Number32.sh
killBill.sh
gotMilk.sh
bugslife.sh
I get an error - "foreach: Words not parenthesized."
Could it be that 'cat $x' isn't calling the x variable correctly? If so, how do I get the file set up so it's contents can be looped thru?
Any help is appreciated!
If you really really really really have to use tcsh, then the following is the propper way to do it:
#!/usr/bin/tcsh -f
echo please enter files list
set x = $<
foreach line (" `cat $x` ")
echo "$line"
end
It is important to see that the cat command is between <double-quotes>. The difference is that otherwise, the foreach statement will read word-by-word, while the double-quoted version will read line-by-line. The logic of this is ... questionable. Also, I quoted the variable line in the echo statement, because it will actually complain when it hits an empty line.
In bash, you would just do the following nice thing:
#!/usr/bin/env bash
echo "Please enter files list"
file=""
while [[ ! -e $file ]]; do read -r file; done
while read -r line; do
echo "$line"
done < "$file"
Very important reads:
CSH PROGRAMMING CONSIDERED HARMFUL
Top Ten Reasons not to use the C shell
For reference purposes, I was missing the cat command, before the $x.
So the code should read:
#! /usr/bin/tcsh -f
echo please enter files list
set x = $<
foreach i ('cat $x')
echo $i
end
You have two mistakes in your tcsh script:
Missing cat command in front of the filename in your foreach condition.
Use of straight single quotes(' ') instead of backquotes (``) in your foreach condition.
The following script should work for you.
#!/usr/bin/tcsh -f
echo please enter files list
set x = $<
foreach i(`cat $x`)
echo $i
end
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).
I have the following hacking-challenge, where we don't know, if there is a valid solution.
We have the following server script:
read s # read user input into var s
echo "$s"
# tests if it starts with 'a-f'
echo "$s" > "/home/user/${s}.txt"
We only control the input "$s". Is there a possibility to send OS-commands like uname or do you think "no way"?
I don't see any avenue for executing arbitrary commands. The script quotes $s every time it is referenced, so that limits what you can do.
The only serious attack vector I see is that the echo statement writes to a file name based on $s. Since you control $s, you can cause the script to write to some unexpected locations.
$s could contain a string like bob/important.txt. This script would then overwrite /home/user/bob/important.txt if executed with sufficient permissions. Sorry, Bob!
Or, worse, $s could be bob/../../../etc/passwd. The script would try to write to /home/user/bob/../../../etc/passwd. If the script is running as root... uh oh!
It's important to note that the script can only write to these places if it has the right permissions.
You could embed unusual characters in $s that would cause irregular file names to be created. Un-careful scripts could be taken advantage of. For example, if $s were foo -rf . bar, then the file /home/user/foo -rf . bar.txt would be created.
If someone ran for file in /home/user; rm $file; done they'd have a surprise on their hands. They would end up running rm /home/user/foo -rf . bar.txt, which is a disaster. If you take out /home/user/foo and bar.txt you're left with rm -rf . — everything in the current directory is deleted. Oops!
(They should have quoted "$file"!)
And there are two other minor things which, while I don't know how to take advantage of them maliciously, do cause the script to behave slightly differently than intended.
read allows backslashes to escape characters like space and newline. You can enter \space to embed spaces and \enter to have read parse multiple lines of input.
echo accepts a couple of flags. If $s is -n or -e then it won't actually echo $s; rather, it will interpret $s as a command-line flag.
Use read -r s or any \ will be lost/missinterpreted by your command.
read -r s?"Your input: "
if [ -n "${s}" ]
then
# "filter" file name from command
echo "${s##*/}" | sed 's|^ *\([[:alnum:]_]\{1,\}\)[[:blank:]].*|/home/user/\1.txt|' | read Output
(
# put any limitation on user here
ulimit -t 5 1>/dev/null 2>&1
`${read}`
) > ${OutPut}
else
echo "Bad command" > /home/user/Error.txt
fi
Sure:
read s
$s > /home/user/"$s".txt
If I enter uname, this prints Linux. But beware: this is a security nightmare. What if someone enters rm -rf $HOME? You'd also have issues with commands containing a slash.
I am foxed by the following situation.
I have a file list.txt that I want to run through line by line, in a loop, in bash. A typical line in list.txt has spaces in. The problem is that the loop contains a "read" command. I want to write this loop in bash rather than something like perl. I can't do it :-(
Here's how I would usually write a loop to read from a file line by line:
while read p; do
echo $p
echo "Hit enter for the next one."
read x
done < list.txt
This doesn't work though, because of course "read x" will be reading from list.txt rather than the keyboard.
And this doesn't work either:
for i in `cat list.txt`; do
echo $i
echo "Hit enter for the next one."
read x
done
because the lines in list.txt have spaces in.
I have two proposed solutions, both of which stink:
1) I could edit list.txt, and globally replace all spaces with "THERE_SHOULD_BE_A_SPACE_HERE" . I could then use something like sed, within my loop, to replace THERE_SHOULD_BE_A_SPACE_HERE with a space and I'd be all set. I don't like this for the stupid reason that it will fail if any of the lines in list.txt contain the phrase THERE_SHOULD_BE_A_SPACE_HERE (so malicious users can mess me up).
2) I could use the while loop with stdin and then in each loop I could actually launch e.g. a new terminal, which would be unaffected by the goings-on involving stdin in the original shell. I tried this and I did get it to work, but it was ugly: I want to wrap all this up in a shell script and I don't want that shell script to be randomly opening new windows. What would be nice, and what might somehow be the answer to this question, would be if I could figure out how to somehow invoke a new shell in the command and feed commands to it without feeding stdin to it, but I can't get it to work. For example this doesn't work and I don't really know why:
while read p; do
bash -c "echo $p; echo ""Press enter for the next one.""; read x;";
done < list.txt
This attempt seems to fail because "read x", despite being in a different shell somehow, is still seemingly reading from list.txt. But I feel like I might be close with this one -- who knows.
Help!
You must open as a different file descriptor
while read p <&3; do
echo "$p"
echo 'Hit enter for the next one'
read x
done 3< list.txt
Update: Just ignore the lengthy discussion in the comments below. It has nothing to do with the question or this answer.
I would probably count lines in a file and iterate each of those using eg. sed. It is also possible to read infinitely from stdin by changing while condition to: while true; and exit reading with ctrl+c.
line=0 lines=$(sed -n '$=' in.file)
while [ $line -lt $lines ]
do
let line++
sed -n "${line}p" in.file
echo "Hit enter for the next ${line} of ${lines}."
read -s x
done
AWK is also great tool for this. Simple way to iterate through input would be like:
awk '{ print $0; printf "%s", "Hit enter for the next"; getline < "-" }' file
As an alternative, you can read from stderr, which by default is connected to the tty as well. The following then also includes a test for that assumption:
(
tty -s <& 2|| exit 1
while read -r line; do
echo "$line"
echo 'Hit enter'
read x <& 2
done < file
)
I have written a simple shell script to do some automation work. Basically the script searches for all the files in the current path and if the file is a specified one, it does some action.
Below are the relevant lines ---
#!/bin/bash
for i in `ls *`
do
if [$i =="ls.sh"]
then .... //do something
fi
done
However, the string comparision in line 3 is not working and I am getting this when I run the script --
./ls.sh: line 3: [scripth.sh: command not found
./ls.sh: line 3: [scripth.sh~: command not found
./ls.sh: line 3: [test.sh: command not found
What is the correction to be done ?
first of all, don't use ls like that. It will go bonkers if your files have spaces!.
Use shell expansion. Then, you can use case/esac to make string comparison. (or if/else)
for file in *
do
case "$file" in
"ls.sh" ) echo "do something"
;;
esac
done
There are several problems.
In line 1, you are not doing what you think you are. You should put a backquote around ls *:
for i in `ls *`
That will go through all files that list in the current directory. Your line will not run any command, but instead it will use * to get all files and your list will include a word "ls" at the front.
try this from a command line:
echo ls *
echo `ls *`
You might just want to do:
for i in *
Second problem. Put spaces inside your square brackets:
[ $i == "ls.sh" ]
The spaces are necessary.
Third problem. Use one = for string comparison
[ $i = "ls.sh" ]
Use: if [ "$i" = "ls.sh" ] - notice the spaces.
If you only want to check the existing of a file, you could do it directly in shell.
if [ -e ls.sh ]
then
# ... do something
fi
You have not included a space after ==, so your code should actually be:
#!/bin/bash
for i in `ls *`
do
if [ $i == "ls.sh" ]
then
//do something
fi
done