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
Related
In order to update some files only if their desired content has changed, I have been using this script:
updatefile()
{
# First parameter is file name.
# Second parameter is desired content.
local FILENAME
local DIRNAME
local FILEALREADYMATCHING
local CURRENTFILECONTENT
if [ $# -ne 2 ] ; then
return 99
fi
FILENAME=$(basename "$1")
DIRNAME=$(dirname "$FILENAME")
FILECONTENT="$2"
mkdir -p "$DIRNAME"
if [ -d "$DIRNAME" ] ; then
FILEALREADYMATCHING=true
if ! [ -f "$FILENAME" ] ; then
FILEALREADYMATCHING=false
else
CURRENTFILECONTENT="$(IFS= cat "$FILENAME")X"
CURRENTFILECONTENT=${CURRENTFILECONTENT%?}
if [ "$CURRENTFILECONTENT" != "$FILECONTENT" ] ; then
FILEALREADYMATCHING=false
fi
fi
if [ "$FILEALREADYMATCHING" != "true" ] ; then
printf '%s' "$2" > "$FILENAME"
fi
fi
}
But I found out that it carries on rewriting the file even when its current content is already matching the desired content. All the X appending and removing gymnastics did not help. Debug-printing the current and desired content shows no difference. What is wrong with the comparison I am using?
Alternatively, is there a standard way of changing a file's content without wearing off the drive? If it matters, I am using the most recent version of Bash on OpenWRT, but the needless overwriting also occurs on Debian testing, amd64.
Your code actually works perfectly fine when I tried it, however;
This may be where it's falling apart:
FILENAME=$(basename "$1") # <-- Path is REMOVED here
DIRNAME=$(dirname "$FILENAME") # <-- No point running, path is already gone
FILECONTENT="$2"
Say i have a file: /tmp/testfile
If i run basename /tmp/testfile I get: testfile
If I then run dirname testfile it doesn't magically find the original path again.
Reverse the order of argument assignment and try again:
DIRNAME=$(dirname "$1") # <-- No point running, path is already gone
FILENAME=$(basename "$1") # <-- Path is REMOVED here
FILECONTENT="$2"
Sidenote
Why don't you add some debug code to it to see where it's falling apart.
I find this sometimes help when bash -x is just a bit busy
#!/bin/bash
# First parameter is file name.
# Second parameter is desired content.
decho () {
if [ "$debug" == "1" ]; then
echo "$#"
return
fi
}
updatefile () {
local FILENAME
local DIRNAME
local FILEALREADYMATCHING
local CURRENTFILECONTENT
if [ $# -ne 2 ] ; then
decho returning #echo if debug=1
return 99
fi
FILENAME=$(basename "$1")
DIRNAME=$(dirname "$FILENAME")
FILECONTENT="$2"
mkdir -p "$DIRNAME"
if [ -d "$DIRNAME" ] ; then
FILEALREADYMATCHING=true
if ! [ -f "$FILENAME" ] ; then
FILEALREADYMATCHING=false
decho "file doesn't exist" #echo if debug=1
else
CURRENTFILECONTENT="$(IFS= cat "$FILENAME")X"
CURRENTFILECONTENT=${CURRENTFILECONTENT%?}
decho "file exists" #echo if debug=1
if [ "$CURRENTFILECONTENT" != "$FILECONTENT" ] ; then
decho "content is different" #echo if debug=1
FILEALREADYMATCHING=false
fi
fi
if [ "$FILEALREADYMATCHING" != "true" ] ; then
decho "file doesnt match, making it match now" #echo if debug=1
decho "file is $FILENAME by the way" #echo if debug=1
printf '%s' "$2" > "$FILENAME"
fi
fi
}
updatefile "$1" "$2"
So then, instead of invoking the script like this:
./script filename "Content"
invoke it like this:
debug=1 ./script filename "Content"
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.
I'm writing my first bash script and having trouble assigning a file path to a variable:
$target="/etc/httpd/conf/httpd.conf"
It seems bash wants to interpret this with the "=" assignment operator resulting in the script throwing an error to the effect "No such file or directory."
Is there an easy way to do this? I've discovered I can assign a full path to a constant like this:
readonly TARGET=/etc/httpd/conf/httpd.conf
but that seems rather cumbersome. How would I perform string ops to modify/manipulate?
I've also discovered I can put full paths in an array like this:
declare -a cfile=('/root/.bashrc' '/etc/fstab')
All well and good, but how do I assign a file path to a variable?
== == == ==
finished! my first bash script - a basic config file manager
#!/bin/bash
# cfmgr.sh - configuration file manager bash script
# options: -get, -put
# '-get' creates SOURCEDIR/USERDIR and copies config files to USERDIR
# '-put' copies files in SOURCEDIR/USERDIR to system-defined locations on server
# purpose: helps with moving LAMP VMs to different hosts, bulk edits of
# of config files in editors like Notepad++, and backing up config files.
readonly SOURCEDIR=/usr/bin/_serverconfig
while [[ $# > 0 ]]
do
arg="$1"
shift
case $arg in
-put)
put=true
;;
-get)
get=true
;;
*)
badarg=true
;;
esac
done
clear
if [ $badarg ]; then
echo "Invalid argument. Use either 'scf.sh -put' or 'scf.sh -get' to put"\
"or get config files."
exit
elif [ $get ]; then
echo "Enter directory name to store files cfmgr will GET from this server:"
elif [ $put ]; then
echo "Enter directory name containing files cfmgr will PUT to this server:"
else
echo "Use either 'scf.sh -put' or 'scf.sh -get' to put or get config files."
exit
fi
read -e -i $SOURCEDIR"/" USERDIR
pattern=" |'"
if [[ $USERDIR =~ $pattern ]]; then
echo "Spaces not allowed. Please try again."
exit
fi
declare -a cfile=('/root/.bashrc' '/etc/fstab' '/etc/hosts' '/etc/networks'\
'/etc/php.ini' '/etc/nsswitch.conf' '/etc/ntp.conf' '/etc/resolv.conf'\
'/etc/sysctl.conf' '/etc/httpd/conf/httpd.conf' '/etc/selinux/config'\
'/etc/samba/smb.conf' '/etc/samba/smbusers' '/etc/security/limits.conf'\
'/etc/sysconfig/network' '/etc/sysconfig/network-scripts/ifcfg-eth0'\
'/etc/sysconfig/network-scripts/ifcfg-eth1')
if [ $get ]; then
if [[ -d "$USERDIR" ]]; then
echo $USERDIR "directory already exists. Please try again."
exit
else
mkdir -m 755 $USERDIR
fi
for file in ${cfile[#]}
do
if [ -e $file ]; then
rsync -q $file $USERDIR
if [ $? -eq 0 ]; then
sleep 0.1
printf "# "$file"\n"
fi
else
printf "not found: "$file"\n"
fi
done
elif [ $put ]; then
if [[ ! -d "$USERDIR" ]]; then
echo $USERDIR "directory does not exist. Please try again."
exit
fi
id=0
cd $USERDIR
for item in *
do
if [[ -f $item ]]; then
cdir[$id]=$item
id=$(($id+1))
fi
done
for file in ${cdir[#]}
do
case $file in
.bashrc)
idx=0
;;
fstab)
idx=1
;;
hosts)
idx=2
;;
networks)
idx=3
;;
php.ini)
idx=4
;;
nsswitch.conf)
idx=5
;;
ntp.conf)
idx=6
;;
resolv.conf)
idx=7
;;
sysctl.conf)
idx=8
;;
httpd.conf)
idx=9
;;
config)
idx=10
;;
smb.conf)
idx=11
;;
smbusers)
idx=12
;;
limits.conf)
idx=13
;;
network)
idx=14
;;
ifcfg-eth0)
idx=15
;;
ifcfg-eth1)
idx=16
;;
*)
printf "not found: "$file"\n"
continue
esac
target=${cfile[$idx]}
if [[ -e $target ]]; then
dtm=$(date +%Y-%m-%d)
mv $target $target"."$dtm
fi
source=$USERDIR"/"$file
dos2unix -q $source
rsync -q $source $target
if [ $? -eq 0 ]; then
sleep 0.1
printf "# "$target"\n"
fi
done
read -p "reboot now? (y|n)" selection
case $selection in
[Yy]*)
`reboot`
;;
*)
exit
;;
esac
fi
exit 0
Instead of
$target="/etc/httpd/conf/httpd.conf"
Use:
target="/etc/httpd/conf/httpd.conf"
When bash sees the former, it first substitutes in for "$target". If target was empty, then the line that bash tries to execute, after the variable substitution and quote removal steps, is:
=/etc/httpd/conf/httpd.conf
Since there is no file named "=/etc/httpd/conf/httpd.conf", bash returns with a "No such file or directory" error.
I have no bash scripting knowledge unforunately. I need a script that reads a cd copied ONE file from the cd to a destination and renames it. Here is my code
#!/bin/bash
mount /dev/cd0 /mnt/
for file in /mnt/*
do
if($file == SO_CV*)
cp SO_CV* /usr/castle/np_new/CVFULLPC.BIN
else if($file == SO_PC*)
cp SO_PC* /usr/castle/np_new/PCMAP.BIN
else if($file == MS_PC*)
cp MS_PC* /usr/castle/np_new/FULLPC.BIN
else if($file == MS_MC*)
cp MS_MC* /usr/castle/np_new/MBFULLPC.BIN
done
umount /mnt/
Could someone tell me if this is even valid bash scripting, or what mistakes I might have made.
Thanks
Jim
Syntax problems. Try this code:
#!/bin/bash
mount /dev/cd0 /mnt/
for file in /mnt/*; do
if [[ "$file" == SO_CV* ]]; then
cp SO_CV* /usr/castle/np_new/CVFULLPC.BIN
elif [[ "$file" == SO_PC* ]]; then
cp SO_PC* /usr/castle/np_new/PCMAP.BIN
elif [[ "$file" == MS_PC* ]]; then
cp MS_PC* /usr/castle/np_new/FULLPC.BIN
elif [[ "$file" == MS_MC* ]]; then
cp MS_MC* /usr/castle/np_new/MBFULLPC.BIN
fi
done
umount /mnt/
an alternative:
#!/bin/bash
error_in_cp () {
{ printf "An ERROR occured while trying to copy: '\s' to its dest file.\n" "$#"
printf "Maybe there were more than 1 file ? or you didn't have the rights necessary to write the destination?"
printf "Exiting..."
} >&2 #to have it on STDERR
exit 1
}
mount /dev/cd0 /mnt/ &&
for file in /mnt/*; do
case "$file" in
SO_CV*) cp -p SO_CV* /usr/castle/np_new/CVFULLPC.BIN || error_in_cp "$file" ;;
SO_PC*) cp -p SO_PC* /usr/castle/np_new/PCMAP.BIN || error_in_cp "$file" ;;
MS_PC*) cp -p MS_PC* /usr/castle/np_new/FULLPC.BIN || error_in_cp "$file" ;;
MS_MC*) cp -p MS_MC* /usr/castle/np_new/MBFULLPC.BIN || error_in_cp "$file" ;;
*) echo "oops, forgot to handle that case: '$file' . ABORTING. "
exit 1
;;
esac
done # no "&&" here so you always umount /mnt/ even if you aborted the copy or the latest command went wrong
umount /mnt/
note: I changed the "cp" to "cp -p" to prevere rights & times... adjust if needed.
note that "&&" at the end of the line is ok
(no need to :
command && \
something
)
You may need to add { and } around each part if there is more than 1 element (here, "case ... esac" is one element, so it's fine)
I'm trying to adapt a bash script from "Sams' Teach Yourself Linux in 24 Hours" which is a safe delete command called rmv. The files are removed by calling rmv -d file1 file2 etc. In the original script a max of 4 files can by removed using the variables $1 $2 $3 $4.
I want to extend this to an unlimited number of files by using a wildcard.
So I do:
for i in $*
do
mv $i $HOME/.trash
done
The files are deleted okay but the option -d of the command rmv -d is also treated as an argument and bash objects that it cannot be found. Is there a better way to do this?
Thanks,
Peter
#!/bin/bash
# rmv - a safe delete program
# uses a trash directory under your home directory
mkdir $HOME/.trash 2>/dev/null
# four internal script variables are defined
cmdlnopts=false
delete=false
empty=false
list=false
# uses getopts command to look at command line for any options
while getopts "dehl" cmdlnopts; do
case "$cmdlnopts" in
d ) /bin/echo "deleting: \c" $2 $3 $4 $5 ; delete=true ;;
e ) /bin/echo "emptying the trash..." ; empty=true ;;
h ) /bin/echo "safe file delete v1.0"
/bin/echo "rmv -d[elete] -e[mpty] -h[elp] -l[ist] file1-4" ;;
l ) /bin/echo "your .trash directory contains:" ; list=true ;;
esac
done
if [ $delete = true ]
then
for i in $*
do
mv $i $HOME/.trash
done
/bin/echo "rmv finished."
fi
if [ $empty = true ]
then
/bin/echo "empty the trash? \c"
read answer
case "$answer" in
y) rm -i $HOME/.trash/* ;;
n) /bin/echo "trashcan delete aborted." ;;
esac
fi
if [ $list = true ]
then
ls -l $HOME/.trash
fi
You can make use of shift here.
Once you find -d is one of the options in the switch, you can shift and get rid of -d from the positional parameters. Next you can
until [ -z $1 ] ; do
mv $1 $HOME/.trash
shift
done
getopts sets OPTIND to the index of the first argument after the options. (#)
So after parsing the options you can do:
shift $OPTIND-1
to remove the options from the argument list.
Then use "$#" rather than $*, and you can handle files with spaces in them.
Thanks a lot!
I changed the code to read:
#!/bin/bash
# rmv - a safe delete program
# todo: add ability to handle wildcards
# uses a trash directory under your home directory
mkdir $HOME/.trash 2>/dev/null
# four internal script variables are defined
cmdlnopts=false
delete=false
empty=false
list=false
# uses getopts command to look at command line for any options
while getopts "dehl" cmdlnopts; do
case "$cmdlnopts" in
d ) echo -e "deleting: \n" "${#:2}" ; delete=true ;;
e ) echo -e "emptying the trash..." ; empty=true ;;
h ) echo -e "safe file delete v1.0"
echo -e "rmv -d[elete] -e[mpty] -h[elp] -l[ist] file [...]" ;;
l ) echo -e "your .trash directory contains:" ; list=true ;;
esac
done
shift $OPTIND-1
if [ $delete = true ]
then
for i in $#
do
mv $i $HOME/.trash
done
echo "rmv finished."
fi
then
/bin/echo "empty the trash? \c"
read answer
case "$answer" in
y) rm -i $HOME/.trash/* ;;
n) /bin/echo "trashcan delete aborted." ;;
esac
fi
if [ $list = true ]
then
ls -l $HOME/.trash
fi
This deletes the files as desired but I get this error:
/home/peter/rmv: line 21: shift: 2-1: numeric argument required
mv: invalid option -- 'd'
Try `mv --help' for more information.
You need to use
shift $(($OPTIND - 1))
to get red of the processed command line args. Try this version:
#!/bin/bash
# rmv - a safe delete program
# uses a trash directory under your home directory
mkdir -p $HOME/.trash
# uses getopts command to look at command line for any options
while getopts "dehl" cmdlnopts; do
case "$cmdlnopts" in
d ) delete=true;;
e ) echo "emptying the trash..." ; empty=true ;;
h ) echo "safe file delete v1.0"
echo "rmv -d[elete] -e[mpty] -h[elp] -l[ist] files" ;;
l ) echo "your .trash directory contains:" ; list=true ;;
esac
done
shift $(($OPTIND - 1))
if [ -n "${delete}" ]; then
echo "deleting: " "${#}"
mv ${#} $HOME/.trash
echo "rmv finished."
fi
if [ -n "${empty}" ]; then
read -p "empty the trash? " answer
case "$answer" in
y) rm -i $HOME/.trash/* ;;
n) echo "trashcan delete aborted." ;;
esac
fi
if [ -n "${list}" ]; then
ls -l $HOME/.trash
fi
Late to the party, but for Googlers, this will generate the error Peter describes:
shift $OPTIND-1
while the syntax in Jurgen's reply will not:
shift $(($OPTIND-1))
The problem is that $OPTIND-1 is interpreted as a string, and shift can't use a string as an argument. $(( )) is Bash's arithmetic expansion operator. You put a string inside it, the string is evaluated as an arithmetic expression, and the value returned.