linux shell append variable parameters to command - linux

I am trying to get a bash script that generates JSDoc for given parameters like this
./jsdoc.sh file.js another.js maybe-a-third.js
I am getting stuck on how to pass an unknown quantity of parameters to the next shell command.
(also, don't know how to check if param exists, only if not exitst if [ -z ... ])
This code works for up to two parameters, but obviously not the right way to go about it...
#!/bin/bash
# would like to know how to do positive check
if [ -z "$1" ]
then echo no param
else
d=$PWD
cd ~/projects/jsdoc-toolkit/
# this bit is obviously not the right approach
if [ -z "$2" ]
then java -jar jsrun.jar app/run.js -a -t=templates/jsdoc/ $d/$1
else java -jar jsrun.jar app/run.js -a -t=templates/jsdoc/ $d/$1 $d/$2
fi
cp -R out/jsdoc $d
fi
Any other pointers of how I could achieve this would be appreciated.
Edit: Updated script according to #skjaidev's answer - happy days ;)
#!/bin/bash
d=$PWD
for i in $*; do
params=" $params $d/$i"
done
if [ -n "$1" ]; then
cd ~/projects/jsdoc-toolkit/
java -jar jsrun.jar app/run.js -a -t=templates/jsdoc/ $params
cp -R out/jsdoc $d
fi

$* has all the parameters. You could iterate over them
for i in $*;
do
params=" $params $d/$i"
done
your_cmd $params

To handle arguments that contain whitespace, use "$#" to iterate, and store the for later use in an array.
#!/bin/bash
if (( $# == 0 )); then
echo "usage: $0 file ..."
exit
fi
dir=$(pwd)
declare -a params
for file in "$#"; do params+=( "$dir/$file" ); done
cd ~/projects/jsdoc-toolkit/
java -jar jsrun.jar app/run.js -a -t=templates/jsdoc/ "${params[#]}"
cp -R out/jsdoc "$dir"

-n is the inverse of -z, and "$#" is the canonical way to pass all parameters on to a subcommand. This and more can be found via man bash.

Bash-specific features (arrays in this case) can be avoided through careful use of the IFS variable and the special $# parameter.
#!/bin/sh
dir=$(pwd)
NEW_ARGV=""
# carefully modify original arguments individually and separate them with newlines
# in a new variable (in case they contain spaces)
NEW_ARGV=""
for var in "${#}"
do
NEW_ARGV="${NEW_ARGV}
${dir}/${var}"
done
SV_IFS=${IFS}
# temporarily set IFS to a newline as per NEW_ARGV setup
IFS="
"
# reset $# with the modified newline-separated arguments
set -- ${NEW_ARGV}
IFS=${SV_IFS}
# for testing, demonstrate each param is preserved
c=0
for i in "${#}"
do
c=`expr ${c} + 1`
echo "args-via-var #${c}: \"${i}\""
done
cd ~/projects/jsdoc-toolkit/
java -jar jsrun.jar app/run.js -a -t=templates/jsdoc/ "${#}"
cp -R out/jsdoc "${dir}"
It's not necessary to reset $#, but doing so avoids messing with IFS in multiple places. Without going through $# one must set IFS everywhere one expands $NEW_ARGV.
Those with an eye for detail will note that this method does not preserve parameters when they contain newlines. It would be possible to use any control character in place of newline, except of course NUL, and perhaps ASCII FS (file separator, aka ctrl-\) would be both meaningful and very unlikely to occur in a valid filename.

Related

Shell Scripting to Compress directory [duplicate]

This question already has an answer here:
Shell spacing in square brackets [duplicate]
(1 answer)
Closed 4 years ago.
$1 is file / folder that want to compressed
Output filename is the same name, plus current date and ext
if output name exist, then just give warning
Example:
./cmp.sh /home/user
It will be /home/user to /home/user_2018-03-11.tar.bz2
i already have lead, but i'm stuck
#!/bin/bash
if ["$1" == ""]; then
echo "Help : To compress file use argument with directory"
exit 0
fi
if [[ -f "$1" || -d "$1" ]]; then
tar -cvjSf $1"_"$(date '+%d-%m-%y').tar.bz2 $1
fi
but the output is _22-04-2018.tar.bz2
I see that you're using quotes to avoid the problem the underscore getting used as part of the variable name. So while $1 is a positional paramater, $1_ is a variable that you have not set in your script. You can avoid this issue by using curly braces, like ${1}. Anything inside the braces is part of the variable name, so ${1}_ works. This notation would be preferable to $1"_" which leaves a user-provided variable outside of quotes. (Of course, "$1"_ would do the job as wel.)
Also, it's probably safer to set the filename in a variable, then use that for all your needs:
#!/bin/bash
if [ -z "$1" ]; then
echo "Help : To compress file use argument with directory"
exit 0
fi
filename="${1}_$(date '+%F').tar.bz2"
if [ -e "$filename" ]; then
echo "WARNING: file exists: $filename" >&2
else
tar -cvjSf "$filename" "$#"
fi
Changes:
you need spaces around your square brackets in an if condition,
while you can test for equivalence to a null string, -z is cleaner, though you could also test for [ $# -eq 0 ], counting the parameters provided,
using $filename makes sure that your test and your tar will always use the same name, even if the script runs over midnight, and is way more readable,
variables should always be quoted.
Also, are you sure about the -S option for tar? On my system, that option extracts sparse files, and is only useful in conjunction with -x.
ALSO, I should note that as I've rewritten it, there's nothing in this script which is specific to bash, and it should be portable to POSIX shells as well (ash/dash/etc). Bash is great, but it's not universal, and if through your learning journey you can learn both, it will give you useful skills across multiple operating systems and environments.
Use -z switch to check if blank
#!/bin/bash
if [[ -z "$1" ]]; then
echo "Help : To compress file use argument with directory"
exit 0
fi
if [[ -f "$1" || -d "$1" ]]; then
tar -cvjSf $1"_"$(date '+%d-%m-%y').tar.bz2 $1
fi

I keep getting a 'while syntax' error on the output of the at job in unix and I have no idea why

#!/usr/dt/bin/dtksh
while getopts w:m: option
do
case $option in
w) wflag=1
wval="$OPTARG";;
m) mflag=1
mval="$OPTARG";;
?) printf 'BAD\n' $0
exit 2;;
esac
done
if [ ! -z "$wflag" ]; then
printf "W and -w arg is $wval\n"
fi
if [ ! -z "$mflag" ]; then
printf "M and -m arg is $mval\n"
fi
shift $(($OPTIND - 1))
printf "Remaining arguments are: $* \n"
at $wval <<ENDMARKER
echo $* >> Search_List
tr " " "\n" <Search_List >Usr_List
while true; do
if [ -s Usr_List ]; then
for i in $(cat Usr_List); do
if finger -m | grep $i; then
echo '$i is online' | elm user
sed '/$i/d' <Usr_List >tmplist
mv tmplist Usr_List
fi
done
else
break
fi
done
ENDMARKER
Essentially I want to keep searching through until it is empty. Each time an element of the list is found, it is deleted. Once the list is empty quit.
There are no error messages when I first run the command, it only shows up in an email containing the output of the at job.
Thanks in advance for any advice
EDIT: The script uses getopts and takes one argument for -w and one for -m, the w value is set as the time for the at job, the m still has to be used. Any arguments after the one for m are sent to a file called Search_List, Search_List is edited and saved as Usr_List. Then in the while loop, while Usr_List is not empty, the script checks the results of finger -m against the names in Usr_List. If a name is found, it is removed from Usr_List. Once Usr_List is empty, the program should stop.
elm is a way to send an email, so elm user sends an email to user.
The error is :
while: Expression syntax
at uses /bin/sh by default.
at now <<ENDMARKER
<code here>
ENDMARKER
All of this executes under /bin/sh, which on some systems can be Bourne Shell (Solaris for example).
You need to figure out what /bin/sh is for your system, then modify things accordingly. Plus, read the gurantees about what is and what is not in your "at" environment. I think the problem lies there. You have both UNIX and linux tags. So I cannot give a lot more help than that.
You can enable logging -- the way YOU need it -- of the at code chunk:
exec 2&>1 > /tmp/somefile.log
Then write debugging messages to stdout or stderr.
Your HEREDOC is being interpolated. Try quoting the delimiter:
at $wval << 'ENDMARKER'
Although ( I haven't looked closely) it appears that you want some interpolation. But you definitely do not want it on the line in which you reference $i, so quote that $ if you do not quote the entire heredoc:
if finger -m | grep \$i; then
You need to pass the -k option to at:
...
at -k $wval <<ENDMARKER
...
at is otherwise defaulting to your login shell which is csh or one of its derivatives.
It turns out that the while command and the if command needed to be combined.
while [[ -s Usr_List ]]; do
......
done

keeping track of a moving shell script

I hope someone can help me out. For the past month or so I have be learning the Bash... I have a program ( a simple language study program ) that I want to be able to install and run from a script.
I have a script that will create a new folder and move itself into it. The way I am doing it at the moment is below, although I have had problems with arrays that I am using later. I was wondering if there was a cleaner way of getting the new path to file name. Any help or insight would be greatly appreciated.
#!/bin/bash
echo "# path to me ---------------> ${0} "
echo "# parent path --------------> ${0%/*} "
echo "# my name ------------------> ${0##*/} "
if [[ ! -d ${0%/*}/SomeNewFolder ]] && [[ ! -d ${0%/*}/../SomeNewFolder ]]
then
mkdir ${0%/*}/SomeNewFolder
mv ${0} ${0%/*}/SomeNewFolder/${0##*/}
fi
echo ${0%/*}
newpath=$(echo "${0%/*}/SomeNewFolder")
echo $newpath
All the best, Ben
exit
For clarity, I would probably declare named variables for your common values instead of constantly reusing the ${0} array. It's also good practice to quote variables and strings.
The only major issue I saw, was running ./script.sh would make $0 equal just the filename, so I add "./" to the beginning in that case.
#!/bin/bash -u
ME="${0}"
if [[ ! "$ME" =~ /^\// ]]; then
ME="./$ME"
fi
PARENT="${ME%/*}"
FILENAME="${ME##*/}"
FOLDER="SomeNewFolder"
NEW="$PARENT/$FOLDER"
if [[ ! -d "$NEW" ]] && [[ "${PARENT%/*}" != "$FOLDER" ]]; then
mkdir "$NEW"
mv "$ME" "$NEW"
fi
echo "$PARENT"
echo "$NEW"
Well, you could do something like this to get an absolute path:
PARENTPATH=$( cd "$( dirname "$0" )" && pwd )
NEWPATH=${PARENTPATH}/SomeNewFolder
me="$0"
newdir=SomeNewFolder
if [[ $me =~ ^/ ]] ; then
full_path="$me"
else
full_path="$PWD/$me"
fi
full_path="${full_path//\/\.\///}" # prettify
path_to_me="${full_path%/*}"
parent_dir="${path_to_me##*/}"
if [ ! "$parent_dir" = "$newdir" ] ; then
mkdir -p "$path_to_me/$newdir"
mv -f "$full_path" "$path_to_me/$newdir/"
fi
Basically similar to what lunixbochs was doing, but with a few minor alterations
lower case variable names so as not to be confused with environment variables
crudely estimates absolute path
-f and -p becuase interactivity is never cool, and why not
Installing and setting up programs is more appropriately done from a make file. Granted, it seems intimidating at first, but the basics, such as what you want, are quite simple. For your project, you would ideally have three item:
your program
your run script
your makefile i.e. your installer
This breaks apart each of these different components, making each of them easier to manage. If you tar them together, you can move the tar file to a new computer and reinstall without any changes. Bash is a wonderful tool, but an installer it is not.
Sample make script below:
.PHONY: all clean
SCRIPT=yourScriptName.sh
SUBFOLDER=someFolder
all: $(SCRIPT)
$(SCRIPT): $(SUBFOLDER)
cp $(SCRIPT) $(SUBFOLDER)
$(SUBFOLDER):
mkdir $(SUBFOLDER)
clean:
-rm -f $(SUBFOLDER)/$(SCRIPT)
-rmdir $(SUBFOLDER)
IMPORTANT! make is whitespace sensitive! Those indents are tabs not four spaces.

busybox sh wrapper to add extra functionality

I need a simple busybox sh wrapper which will do:
IF "-Q" PARAMETER IS PROVIDED THEN
acommand ALL PARAMETERS BUT "-Q" 2>&1 1>/dev/null
ELSE
acommand ALL PARAMETERS
FI
Parameters may include spaces.
BTW I want to run the script with busybox sh and it doesn't support arrays.
It's possible to do it all in busybox's ash shell:
#!/bin/sh
for i in "${#}"
do
if [ "$i" = "-Q" ]
then
flagQ=1
else
args="$args \"$i\""
fi
done
if [ "$flagQ" = "1" ]
then
eval acommand "$args" 2>&1 1>/dev/null
else
eval acommand "$args"
fi
This uses bash arrays - but I see from the comments to another answer that the code isn't supposed to run under bash (despite the bash tag originally applied to the question); it is meant to run under the busybox shell.
I'm almost certain it doesn't answer the question because the question is substantially unanswerable given the limitations of busybox. In times past, I have used a custom program I called 'escape' to build up an argument string that can be eval'd to get the original arguments - spaces and all. But that requires support from outside the shell.
This solution only uses 'bash'. I'm not sure it is fully idiomatic bash code, but it works.
#!/bin/bash
i=0
Qflag=0
for arg in "$#"
do
if [ "X$arg" = "X-Q" ]
then Qflag=1
else args[$((i++))]=$arg
fi
done
if [ $Qflag = 1 ]
then exec acommand "${args[#]}" 2>&1 >/dev/null
else exec acommand "${args[#]}"
fi
The first loops builds up an array, args, with the arguments to the script, except it doesn't add '-Q' to the list and records its presence in variable Qflag.
The if statement at the end notes whether Qflag was set to 1, and if so, sends the errors from 'acommand' to standard output and sends regular standard output to /dev/null (which is different from the effect if the I/O redirections are reversed - that would send standard output to /dev/null and send standard error to the same place, forcing silence on 'acommand').
The use of 'exec' is a trivial optimization that simplifies exit status handling in this case.
Tested with 'acommand' that prints its arguments on separate lines:
#!/bin/sh
for arg in "$#"
do echo "$arg"
done
and with command lines such as:
bash wrapper.sh -c -d 'arg with spaces'
which produces the output:
-c
-d
arg with spaces
Obviously, with the I/O redirection in place, there is no output from:
bash wrapper.sh -c -Q -d 'arg with spaces'
However, if you omit the I/O redirection, you get to see the same output.
It's a pity that you need to handle spaces in the arguments otherwise this might work:
#!/bin/sh
Q=0
ARGS=
while [ $# -ge 1 ]; do
case $1 in
-Q)
Q=1
;;
*)
ARGS="$ARGS $1"
;;
esac
shift
done
if [ $Q -eq 1 ] ; then
acommand $ARGS 2>&1 1>/dev/null
else
acommand $ARGS
fi
EDIT:
So this version handles spaces, at the expense of interpreting back-ticks.
#!/bin/busybox ash
Q=0
ARGS=
while [ $# -ge 1 ]; do
case $1 in
-Q)
Q=1
;;
*)
ARGS="$ARGS \"$1\""
;;
esac
shift
done
if [ "$Q" -eq 1 ] ; then
eval acommand $ARGS 2>&1 1>/dev/null
else
eval acommand $ARGS
fi
I think to have a complete solution you are going to have to code it in C, which will be a bit ugly.

How to properly handle wildcard expansion in a bash shell script?

#!/bin/bash
hello()
{
SRC=$1
DEST=$2
for IP in `cat /opt/ankit/configs/machine.configs` ; do
echo $SRC | grep '*' > /dev/null
if test `echo $?` -eq 0 ; then
for STAR in $SRC ; do
echo -en "$IP"
echo -en "\n\t ARG1=$STAR ARG2=$2\n\n"
done
else
echo -en "$IP"
echo -en "\n\t ARG1=$SRC ARG2=$DEST\n\n"
fi
done
}
hello $1 $2
The above is the shell script which I provide source (SRC) & desitnation (DEST) path. It worked fine when I did not put in a SRC path with wild card ''. When I run this shell script and give ''.pdf or '*'as follows:
root#ankit1:~/as_prac# ./test.sh /home/dev/Examples/*.pdf /ankit_test/as
I get the following output:
192.168.1.6
ARG1=/home/dev/Examples/case_Contact.pdf ARG2=/home/dev/Examples/case_howard_county_library.pdf
The DEST is /ankit_test/as but DEST also get manupulated due to '*'. The expected answer is
ARG1=/home/dev/Examples/case_Contact.pdf ARG2=/ankit_test/as
So, if you understand what I am trying to do, please help me out to solve this BUG.
I'll be grateful to you.
Thanks in advance!!!
I need to know exactly how I use '*.pdf' in my program one by one without disturbing DEST.
Your script needs more work.
Even after escaping the wildcard, you won't get your expected answer. You will get:
ARG1=/home/dev/Examples/*.pdf ARG2=/ankit__test/as
Try the following instead:
for IP in `cat /opt/ankit/configs/machine.configs`
do
for i in $SRC
do
echo -en "$IP"
echo -en "\n\t ARG1=$i ARG2=$DEST\n\n"
done
done
Run it like this:
root#ankit1:~/as_prac# ./test.sh "/home/dev/Examples/*.pdf" /ankit__test/as
The shell will expand wildcards unless you escape them, so for example if you have
$ ls
one.pdf two.pdf three.pdf
and run your script as
./test.sh *.pdf /ankit__test/as
it will be the same as
./test.sh one.pdf two.pdf three.pdf /ankit__test/as
which is not what you expect. Doing
./test.sh \*.pdf /ankit__test/as
should work.
If you can, change the order of the parameters passed to your shell script as follows:
./test.sh /ankit_test/as /home/dev/Examples/*.pdf
That would make your life a lot easier since the variable part moves to the end of the line. Then, the following script will do what you want:
#!/bin/bash
hello()
{
SRC=$1
DEST=$2
for IP in `cat /opt/ankit/configs/machine.configs` ; do
echo -en "$IP"
echo -en "\n\t ARG1=$SRC ARG2=$DEST\n\n"
done
}
arg2=$1
shift
while [[ "$1" != "" ]] ; do
hello $1 $arg2
shift
done
You are also missing a final "done" to close your outer for loop.
OK, this appears to do what you want:
#!/bin/bash
hello() {
SRC=$1
DEST=$2
while read IP ; do
for FILE in $SRC; do
echo -e "$IP"
echo -e "\tARG1=$FILE ARG2=$DEST\n"
done
done < /tmp/machine.configs
}
hello "$1" $2
You still need to escape any wildcard characters when you invoke the script
The double quotes are necessary when you invoke the hello function, otherwise the mere fact of evaluating $1 causes the wildcard to be expanded, but we don't want that to happen until $SRC is assigned in the function
Here's what I came up with:
#!/bin/bash
hello()
{
# DEST will contain the last argument
eval DEST=\$$#
while [ $1 != $DEST ]; do
SRC=$1
for IP in `cat /opt/ankit/configs/machine.configs`; do
echo -en "$IP"
echo -en "\n\t ARG1=$SRC ARG2=$DEST\n\n"
done
shift || break
done
}
hello $*
Instead of passing only two parameters to the hello() function, we'll pass in all the arguments that the script got.
Inside the hello() function, we first assign the final argument to the DEST var. Then we loop through all of the arguments, assigning each one to SRC, and run whatever commands we want using the SRC and DEST arguments. Note that you may want to put quotation marks around $SRC and $DEST in case they contain spaces. We stop looping when SRC is the same as DEST because that means we've hit the final argument (the destination).
For multiple input files using a wildcard such as *.txt, I found this to work perfectly, no escaping required. It should work just like a native bash app like "ls" or "rm." This was not documented just about anywhere so since I spent a better part of 3 days trying to figure it out I decided I should post it for future readers.
Directory contains the following files (output of ls)
file1.txt file2.txt file3.txt
Run script like
$ ./script.sh *.txt
Or even like
$ ./script.sh file{1..3}.txt
The script
#!/bin/bash
# store default IFS, we need to temporarily change this
sfi=$IFS
#set IFS to $'\n\' - new line
IFS=$'\n'
if [[ -z $# ]]
then
echo "Error: Missing required argument"
echo
exit 1
fi
# Put the file glob into an array
file=("$#")
# Now loop through them
for (( i=0 ; i < ${#file[*]} ; i++ ));
do
if [ -w ${file[$i]} ]; then
echo ${file[$i]} " writable"
else
echo ${file[$i]} " NOT writable"
fi
done
# Reset IFS to its default value
IFS=$sfi
The output
file1.txt writable
file2.txt writable
file3.txt writable
The key was switching the IFS (Internal Field Separator) temporarily. You have to be sure to store this before switching and then switch it back when you are done with it as demonstrated above.
Now you have a list of expanded files (with spaces escaped) in the file[] array which you can then loop through. I like this solution the best, easiest to program for and easiest for the users.
There's no need to spawn a shell to look at the $? variable, you can evaluate it directly.
It should just be:
if [ $? -eq 0 ]; then
You're running
./test.sh /home/dev/Examples/*.pdf /ankit_test/as
and your interactive shell is expanding the wildcard before the script gets it. You just need to quote the first argument when you launch it, as in
./test.sh "/home/dev/Examples/*.pdf" /ankit_test/as
and then, in your script, quote "$SRC" anywhere where you literally want the things with wildcards (ie, when you do echo $SRC, instead use echo "$SRC") and leave it unquoted when you want the wildcards expanded. Basically, always put quotes around things which might contain shell metacharacters unless you want the metacharacters interpreted. :)

Resources