I'm back again :(
Still some problem with bash, the question is about to make the script to read an option -r then do some further further operation. I think I make it right but when I tried to run it, I got a feedback saying:" ./stripchars: line 20: -r: No such file or directory". And another one saying:" ./stripchars: line 26: ne: command not found"
Here is my code:
#!/bin/bash
FILE=$1
while getopts "r:" o; do
case "${o}" in
r)
r=${OPTARG}
;;
*)
;;
esac
done
shift $((OPTIND-1))
if [ ! -z "$FILE" ]
then
exec 0< "$FILE"
fi
while IFS='' read -r LINE
do
echo "$LINE" | tr -d '${r}'
done
if [ -z "${r}" ]
then
if [ ! -z "$FILE" ]
then
exec 0< "$FILE"
fi
while IFS='' read -r LINE
do
echo "$LINE" | tr -d '[:punct:]'
done
fi
If the file name really is the first argument (as implied by FILE=$1), then getopts has a non-zero exit status immediately (since the first argument is not an option), and you never enter the loop. You need to change your call to something like
myscript -r whatever foo.txt
and move FILE=$1 after the loop that parses the options.
Related
I am trying to read a parameter file in a shell script and would want to skip the lines which start with "#". Have been trying it on Ubuntu VM (default bash) and for something that I can't understand, it doesn't seem to work.
Following is the pseudo-code that I am using:
while read line
do
if [ grep -q "#" <<< "$line" ]; then
## Do nothing (Commented Out)
echo "$line Line is Commented out"
elif [ "$line" = "" ]; then
## Do nothing (Blank Line)
echo "Blank line"
else
#echo "read line is $line"
...some logic here
fi
done <input_file.ini
This yields the the following error: Syntax error: redirection unexpected
The if [[ $line == *#* ]] construct doesn't seem to work. My earlier experience was on AIX where everything worked fine.
Could someone guide me what I am doing wrong here?
PS: On a related note, how do I handle cases where I don't want to do anything? e.g. when there is no '#' character in the read line, I don't want to do anything. I can't leave my if block blank so I am just using echo 'some random' text. My task works good but just wanted to understand what's a good practice to handle this.
Your code is clearly running with /bin/sh, not bash.
An alternative to [[ $line = *"#"* ]] that works with /bin/sh is case.
Thus, the following will work with /bin/sh, or when invoked with sh yourscript:
#!/bin/sh
while read -r line; do : line="$line"
case $line in
*"#"*) echo "Line is commented out: $line";;
"") echo "Line is empty" ;;
*) key=${line%%=*}
value=${line#*=}
eval "$key="'$line' # unsafe, but works with /bin/sh, which doesn't have better
# indirect assignment approaches.
printf '%s\t\t-\t\t%s\n' "$key" "$value"
;;
esac
done <input_file.ini
Alternately, consider putting in a guard to handle the case when your script is invoked with a non-bash shell:
#!/bin/bash
case $BASH_VERSION in
'')
echo "ERROR: Run with a non-bash shell" >&2
if [ "$tried_reexec" ]; then
echo "ERROR: Already attempted reexec and failed" >&2
exit 1
fi
if [ -s "$0" ]; then
export tried_reexec=1
exec bash "$0" "$#"
fi
;;
esac
while read -r line; do
if [[ $line = *"#"* ]]; then
echo "Line is Commented out: $line"
elif [[ "$line" = "" ]]; then
echo "Blank line"
else
key=${line%%=*}; value=${line#*=}
printf -v "$key" %s "$value"
printf '%s\t\t-\t\t%s\n' "$key" "$value"
fi
done <input_file.ini
I really wasn't able to figure out the exact problem with double [[ ]] and the character search in a string. Thanks to everyone who tried to help me out. However this was acting as a deterrent and I didn't want to continue to fiddle for too long, I used a slightly different approach to handle my situation.
The following code works for me now:
while read line
do
first_char=`echo $line | cut -c 1`
if [ "$first_char" = "#" ]; then
: "do nothing here. Line is commented out"
elif [ "$line" = "" ]; then
: "do nothing here. Blank line"
else
KEY="$(echo $line | cut -d '=' -f1)"
VALUE="$(echo $line | cut -d '=' -f2)"
printf \v "$KEY" %s "$VALUE"
echo "$KEY\t\t-\t\t$VALUE"
fi
done < ${SCHEDULER_LOC}/inputs/script_params.ini
Also I was able to learn few things so incorporated them as well. I did get few negative scores for this question. Understandably so since this might be rudimentary for the experts but it was a genuine problem I was seeking some guidance on. Still, I am thankful that I learnt something new. Kudos to the community.
I have this little bash script that I'm writing to create files in particular directory by reading lines in a file. But the issue is mkdir is not creating dir, not sure why and it is working I try it outside the script. Below is my script...
#!/bin/bash -x
source credentials.sh
OPTARG=""
while getopts :i:x:n name
do
case $name in
x) inputfile="$OPTARG" ;;
i) outputPath="$OPTARGS" ;;
n) dirName="$OPTARG" ;;
esac
done
if [ ! "$dirName" ]
then
mkdir $dirName || echo "error while creating dir"
fi
while read -r line;
do
touch "$line"
mv "$line" "$dirName"
done < $inputfile
ERROR:
[root#Buy]# ./prepare_messages.sh -x file.txt -n testdir
mkdir: missing operand
I searched and tried few but not working, can someone please shed some light...
Thx,
Arun
[ ! "$dirName" ] doesn't test whether the directory exists, it just tests whether the string $dirName is empty or not. You need:
if [ ! -d "$dirName" ]
You should also quote the variable when calling mkdir:
mkdir "$dirName" || echo "error while creating dir"
Your getopts call is wrong. The : character goes after the option that takes the argument. Since you don't have : after n, $OPTARG wasn't being set for that option, so $dirName is always empty. It should be getopts i:x:n: name.
There's also no $OPTARGS variable, that should be $OPTARG.
The full correction:
#!/bin/bash -x
source credentials.sh
OPTARG=""
while getopts i:x:n: name
do
case $name in
x) inputfile="$OPTARG" ;;
i) outputPath="$OPTARG" ;;
n) dirName="$OPTARG" ;;
esac
done
if [ ! -d "$dirName" ]
then
mkdir "$dirName" || echo "error while creating dir"
fi
while read -r line;
do
touch "$line"
mv "$line" "$dirName"
done < "$inputfile"
Got it working, below is corrected line of code. Missing ":" at end of getopts args...
while getopts :i:x:n: name
I am new to bash scripting and I have to create this script that takes 3 directories as arguments and copies in the third one all the files in the first one that are NOT in the second one.
I did it like this:
#!/bin/bash
if [ -d $1 && -d $2 && -d $3 ]; then
for FILE in [ ls $1 ]; do
if ! [ find $2 -name $FILE ]; then
cp $FILE $3
done
else echo "Error: one or more directories are not present"
fi
The error I get when I try to execute it is: "line 7: syntax error near unexpected token `done' "
I don't really know how to make it work!
Also even if I'm using #!/bin/bash I still have to explicitly call bash when trying to execute, otherwise it says that executing is not permitted, anybody knows why?
Thanks in advance :)
Couple of suggestions :
No harm double quoting variables
cp "$FILE" "$3" # prevents wordsplitting, helps you filenames with spaces
for statement fails for the fundamental reason -bad syntax- it should've been:
for FILE in ls "$1";
But then, never parse ls output. Check [ this ].
for FILE in ls "$1"; #drastic
Instead of the for-loop in step2 use a find-while-read combination:
find "$1" -type f -print0 | while read -rd'' filename #-type f for files
do
#something with $filename
done
Use lowercase variable names for your script as uppercase variables are reserved for the system. Check [this].
Use tools like [ shellcheck ] to improve script quality.
Edit
Since you have mentioned the input directories contain only files, my alternative approach would be
[[ -d "$1" && -d "$2" && -d "$3" ]] && for filename in "$1"/*
do
[ ! -e "$2/${filename##*/}" ] && cp "$filename" "$3"
done
If you are baffled by ${filename##*/} check [ shell parameter expansion ].
Sidenote: In linux, although discouraged it not uncommon to have non-standard filenames like file name.
Courtesy: #chepner & #mklement0 for their comments that greatly improved this answer :)
Your script:
if ...; then
for ...; do
if ...; then
...
done
else
...
fi
Fixed structure:
if ...; then
for ...; do
if ...; then
...
fi # <-- missing
done
else
...
fi
If you want the script executable, then make it so:
$ chmod +x script.sh
Notice that you also have other problems in you script. It is better written as
dir1="$1"
dir2="$2"
dir3="$3"
for f in "$dir1"/*; do
if [ ! -f "$dir2/$(basename "$f")" ]; then
cp "$f" "$dir3"
fi
done
this is not totally correct:
for FILE in $(ls $1); do
< whatever you do here >
done
There is a big problem with that loop if in that folder there is a filename like this: 'I am a filename with spaces.txt'.
Instead of that loop try this:
for FILE in "$1"/*; do
echo "$FILE"
done
Also you have to close every if statement with fi.
Another thing, if you are using BASH ( #!/usr/bin/env bash ), it is highly recommended to use double brackets in your test conditions:
if [[ test ]]; then
...
fi
For example:
$ a='foo bar'
$ if [[ $a == 'foo bar' ]]; then
> echo "it's ok"
> fi
it's ok
However, this:
$ if [ $a == 'foo bar' ]; then
> echo "it's ok";
> fi
bash: [: too many arguments
You've forgot fi after the innermost if.
Additionally, neither square brackets nor find do work this way. This one does what your script (as it is now) is intended to on my PC:
#!/bin/bash
if [[ -d "$1" && -d "$2" && -d "$3" ]] ; then
ls -1 "$1" | while read FILE ; do
ls "$2/$FILE" >/dev/null 2>&1 || cp "$1/$FILE" "$3"
done
else echo "Error: one or more directories are not present"
fi
Note that after a single run, when $2 and $3 refer to different directories, those files are still not present in $2, so next time you run the script they will be copied once more despite they already are present in $3.
So I have googled this and thought I found the answers, but it still doesnt work for me.
The program computes the average and median of rows and columns in a file of numbers...
Using the file name works:
./stats -columns test_file
Using cat does not work
cat test_file | ./stats -columns
I am not sure why it doesnt work
#file name was given
if [[ $# -eq 2 ]]
then
fileName=$2
#file name was not given
elif [[ $# -eq 1 ]]
then
#file name comes from the user
fileName=/dev/stdin
#incorrect number of arguments
else
echo "Usage: stats {-rows|-cols} [file]" 1>&2
exit 1
fi
A very simple program that accepts piped input:
#!/bin/sh
stdin(){
while IFS= read -r i
do printf "%s" "$i"
done
}
stdin
Test is as follows:
echo "This is piped output" | stdin
To put that into a script / utility similar to the one in the question you might do this:
#!/bin/sh
stdin(){
while IFS= read -r i
do printf "%s" "$i"
done
}
rowbool=0
colbool=0
for i in $#
do case "$i" in
-rows) echo "rows set"
rowbool=1
shift
;;
-cols) echo "cols set"
colbool=1
shift
;;
esac
done
if [[ $# -gt 0 ]]
then
fileName=$1
fi
if [[ $# -eq 0 ]]
then fileName=$(stdin)
fi
echo "$fileName"
I've the following script.
for args
do
while read line; do
# do something
done <"$args"
done
If the script is started with a list of filenames, it should read out each file line by line.
Now I'm looking for a way the read from stdin when script is started without a list of filenames, but I doesn't want to duplicate the while loop.
Any ideas?
Quick answer:
[[ -z $1 ]] && defaultout=/dev/stdin
for f in "$#" $defaultout; do
while read line; do
# do something
done < "$f"
done
Drawback: parameters are not parsed
Second attempt:
[[ -z $1 ]] && defaultout=/dev/stdin
for f in $# $defaultout; do
if [[ -f $f ]]; then
while read line; do
# do something
done < "$f"
fi
done
Drawback: Filenames with spaces will be parsed into two words.
You could try:
args="$*"
if [ "$args" = "" ]; then
args=/dev/stdin;
fi
for arg in $args; do
while read -r line; do
# do something
done < "$arg";
done
The following should do what you want:
cat "$#" | while read line; do
# something
done