have some problem with shell script.
In our office we set up only few commands, that available for devs when they are trying ssh to server. It is configured with help of .ssh/authorized_keys file and available command for user there is bash script:
#!/bin/sh
if [[ $1 == "--help" ]]; then
cat <<"EOF"
This script has the purpose to let people remote execute certain commands without logging into the system.
For this they NEED to have a homedir on this system and uploaded their RSA public key to .ssh/authorized_keys (via ssh-copy-id)
Then you can alter that file and add some commands in front of their key eg :
command="/usr/bin/dev.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty
The user will do the following : ssh testuser#server tail testserver.example.com/2017/01/01/user.log
EOF
exit 0;
fi
# set global variable
set $SSH_ORIGINAL_COMMAND
# set the syslog path where the files can be found
PATH="/opt/syslog/logs"
# strip ; or any other unwanted signs out of the command, this prevents them from breaking out of the setup command
if [[ $1 != "" ]]; then
COMMAND=$1
COMMAND=${COMMAND//[;\`]/}
fi
if [[ $2 != "" ]]; then
ARGU1=$2
ARGU1=${ARGU1//[;\`]/}
fi
if [[ $3 != "" ]]; then
ARGU2=$3
ARGU2=${ARGU2//[;\`]/}
fi
if [[ $4 != "" ]]; then
ARGU3=$4
ARGU3=${ARGU3//[;\`]/}
fi
# checking for the commands
case "$COMMAND" in
less)
ARGU2=${ARGU1//\.\./}
FILE=$PATH/$ARGU1
if [ ! -f $FILE ]; then
echo "File doesn't exist"
exit 1;
fi
#echo " --------------------------------- LESS $FILE"
/usr/bin/less $FILE
;;
grep)
if [[ $ARGU2 == "" ]]; then
echo "Pls give a filename"
exit 1
fi
if [[ $ARGU1 == "" ]]; then
echo "Pls give a string to search for"
exit 1
fi
ARGU2=${ARGU2//\.\./}
FILE=$PATH/$ARGU2
/usr/bin/logger -t restricted-command -- "------- $USER Executing grep $ARGU1 \"$ARGU2\" $FILE"
if [ ! -f $FILE ]; then
echo "File doesn't exist"
/usr/bin/logger -t restricted-command -- "$USER Executing $#"
exit 1;
fi
/bin/grep $ARGU1 $FILE
;;
tail)
if [[ $ARGU1 == "" ]]; then
echo "Pls give a filename"
exit 1
fi
ARGU1=${ARGU1//\.\./}
FILE=$PATH/$ARGU1
if [ ! -f $FILE ]; then
echo "File doesn't exist"
/usr/bin/logger -t restricted-command -- "$USER Executing $# ($FILE)"
exit 1;
fi
/usr/bin/tail -f $FILE
;;
cat)
ARGU2=${ARGU1//\.\./}
FILE=$PATH/$ARGU1
if [ ! -f $FILE ]; then
echo "File doesn't exist"
exit 1;
fi
/bin/cat $FILE
;;
help)
/bin/cat <<"EOF"
# less LOGNAME (eg less testserver.example.com/YYYY/MM/DD/logfile.log)
# grep [ARGUMENT] LOGNAME
# tail LOGNAME (eg tail testserver.example.com/YYYY/MM/DD/logfile.log)
# cat LOGNAME (eg cat testserver.example.com/YYYY/MM/DD/logfile.log)
In total the command looks like this : ssh user#testserver.example.com COMMAND [ARGUMENT] LOGFILE
EOF
/usr/bin/logger -t restricted-command -- "$USER HELP requested $#"
exit 1
;;
*)
/usr/bin/logger -s -t restricted-command -- "$USER Invalid command $#"
exit 1
;;
esac
/usr/bin/logger -t restricted-command -- "$USER Executing $#"
The problem is next:
when i try to exec some command, it takes only first argument, if i do recursion in files by using {n,n1,n2} - it doesn't work:
[testuser#local ~]$ ssh testuser#syslog.server less srv1838.example.com/2017/02/10/local1.log |grep 'srv2010' | wc -l
0
[testuser#local ~]$ ssh testuser#syslog.server less srv2010.example.com/2017/02/10/local1.log |grep 'srv2010' | wc -l
11591
[testuser#local ~]$ ssh testuser#syslog.server less srv{1838,2010}.example.com/2017/02/10/local1.log |grep 'srv2010' | wc -l
0
[testuser#local ~]$ ssh testuser#syslog.server less srv{2010,1838}.example.com/2017/02/21/local1.log |grep 'srv2010' | wc -l
11591
Could someone help me, how can i parse\count command arguments to make it work?
Thank you and have a nice day!
The number of arguments for a bash script would be $#. As a quick example:
#!/bin/bash
narg=$#
typeset -i i
i=1
while [ $i -le $narg ] ; do
echo " $# $i: $1"
shift
i=$i+1
done
gives, for bash tst.sh a b {c,d}
4 1: a
3 2: b
2 3: c
1 4: d
In your script, the command to execute (cat, less, ...) gets explicitly only the second argument to the script. If you want to read all arguments, you should do something like this (note: only a hint, removed all sorts of checks etc..)
command="$1"
shift
case $command in
(grep) pattern="$1"
shift
while [ $# -gt 0 ] ; do
grep "$pattern" "$1"
shift
done
;;
esac
note: added some quotes as comment suggested, but, being only a hint, you should carefully look at quoting and your checks in your own script.
Less command working now:
case "$COMMAND" in
less)
if [[ $ARGU1 == "" ]]; then
echo "Pls give a filename"
exit 1
fi
FILES_LIST=${#:2}
FILE=(${FILES_LIST//\.\./})
for v in "${FILE[#]}";do
v=${v//[;\']/}
if [ ! -f $v ]; then
echo "File doesn't exist"
fi
/usr/bin/less $PATH/$v
done;;
tail command works too with 2 and more files, but i can't execute tail -f command on two files unfortunately.
Related
I am trying to use the following bash script to login to a remote ftp and delete files older than N days old. Script says it is working and does not give an error - but files are not being deleted. What am I missing? Or is there a better way to do this? Keep in mind this is only a remote FTP and not SSH so I can NOT use the mtime function is why I am trying to do this. Can anyone help?
The usage is all commands - here is what I am using via ssh to run the script
./ftprem.sh -s ftp.server.com -u myusername -p mypassword -f /directory -d 3
#!/bin/bash
PROGNAME=$(basename $0)
OUTFILE="/tmp/ftplist.$RANDOM.txt"
CMDFILE="/tmp/ftpcmd.$RANDOM.txt"
ndays=14
print_usage() {
echo ""
echo "$PROGNAME - Delete files older than N days from an FTP server"
echo ""
echo "Usage: $PROGNAME -s -u -p -f (-d)"
echo ""
echo " -s FTP Server name"
echo " -u User Name"
echo " -p Password"
echo " -f Folder"
echo " -d Number of Days (Default: $ndays)"
echo " -h Show this page"
echo ""
echo "Usage: $PROGNAME -h"
echo ""
exit
}
# Parse parameters
options=':hs:u:p:f:d:'
while getopts $options flag
do
case $flag in
s)
FTPSITE=$OPTARG
;;
u)
FTPUSER=$OPTARG
;;
p)
FTPPASS=$OPTARG
;;
f)
FTPDIR=$OPTARG
;;
d)
ndays=$OPTARG
;;
h)
print_usage
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
shift $(($OPTIND - 1))
if [[ -z "$FTPSITE" || -z "$FTPUSER" || -z "$FTPPASS" || -z "$FTPDIR" ]];
then
echo "ERROR: Missing parameters"
print_usage
fi
# work out our cutoff date
TDATE=`date --date="$ndays days ago" +%Y%m%d`
echo FTP Site: $FTPSITE
echo FTP User: $FTPUSER
echo FTP Password: $FTPPASS
echo FTP Folder: $FTPDIR
echo Removing files older than $TDATE
# get directory listing from remote source
ftp -i -n $FTPSITE <<EOMYF > /dev/null
user $FTPUSER $FTPPASS
binary
cd $FTPDIR
ls -l $OUTFILE
quit
EOMYF
if [ -f "$OUTFILE" ]
then
# Load the listing file into an array
lista=($(<$OUTFILE))
# Create the FTP command file to delete the files
echo "user $FTPUSER $FTPPASS" > $CMDFILE
echo "binary" >> $CMDFILE
echo "cd $FTPDIR" >> $CMDFILE
COUNT=0
# loop over our files
for ((FNO=0; FNO<${#lista[#]}; FNO+=9));do
# month (element 5), day (element 6) and filename (element 8)
FMM=${lista[`expr $FNO+5`]}
FDD=${lista[`expr $FNO+6`]}
FYY=${lista[`expr $FNO+7`]}
if [[ $FYY == *\:* ]]
then
FDATE=`date -d "$FMM $FDD" +'%Y%m%d'`
else
FDATE=`date -d "$FMM $FDD $FYY" +'%Y%m%d'`
fi
# echo $FDATE
# check the date stamp
if [[ $FDATE -lt $TDATE ]];
then
echo "Deleting ${lista[`expr $FNO+8`]}"
echo "delete ${lista[`expr $FNO+8`]}" >> $CMDFILE
COUNT=$[$COUNT + 1]
fi
done
echo "quit" >> $CMDFILE
if [[ $COUNT -gt 0 ]];
then
cat $CMDFILE | tr -d "\r" > $CMDFILE
ftp -i -n $FTPSITE < $CMDFILE > /dev/null
else
echo "Nothing to delete"
fi
rm -f $OUTFILE $CMDFILE
fi
If this helps your debugging...
In the# Parse parameter section of the script, the options variable your have just before the case block has value options=':hs:u:p:f:d:' instead of options=':h:s:u:p:f:d:'
I thought i should point that out.
I have a multiple choice menu script to display top file system disk usage. It is working script but I have problem with one of the file system naming /data.
DATA_RAW_DISK_DUMP="/tmp/$(basename $0).$$.disk_raw_data"
DATA_RAW_FILE_LIST="/tmp/$(basename $0).$$.file_raw_data"
MY_WORKING_DIR=$(pwd)
MY_OUTPUT_FILE="MY_WORKING_DIR/$(basename $0).output"
#check existing output file
if [[ -e $MY_OUTPUT_FILE ]] ; then
if [[ -e ${MY_OUTPUT_FILE}.old ]] ; then
rm ${MY_OUTPUT_FILE}.old
fi
mv $MY_OUTPUT_FILE ${MY_OUTPUT_FILE}.old
fi
#will prompt for choices - ignore any arguments
echo "Please select from below:" cat<<EOF
1. /data
2. /data01
3. DO_ALL_DATA_AT_ONCE
Press ENTER to exit EOF
echo -n "Selection:"
read ans
if [[ $ans == "" ]] ; then
echo "Bye"
exit 0
fi
case $ans in
"1"|"2"|"3")
FS_TARGET="$ans"
;;
*)
echo "Unknown option - quitting"
echo "Bye"
exit 0
;;
esac
DATA="/data0${FS_TARGET}/*"
if [[ $FS_TARGET -eq 3 ]] ; then
#if the selection is 3, then we just do everything, in one go
DATA="/data/* /data01/*"
fi
#do the du first
echo "Running du for ${DATA}, this will take a while, please wait..." >&2
du -sk ${DATA} > $DATA_RAW_DISK_DUMP 2>/dev/null
#we need to check each entry, if its directory, go into it and get the youngest file
echo "Checking file age for ${DATA}, this will take a while, please wait..." >&2
for i in $(cat $DATA_RAW_DISK_DUMP | awk '{print $2}') ; do
if [[ -d $i ]] ; then
#we need to find the youngest file in this directory tree
YOUNGEST_FILE="$(find $i -type f -printf "%C# %p\n" | sort -rn | head -n 1|sed 's,[0-9]*[.][0-9]*[ ],,g')"
if [[ $YOUNGEST_FILE == "${i}" ]] ; then
stat --format "%n;%Y;%X;%U" "$i" >> $DATA_RAW_FILE_LIST 2>/dev/null
elif [[ $YOUNGEST_FILE == "" ]] ; then
stat --format "%n;%Y;%X;%U" "$i" >> $DATA_RAW_FILE_LIST 2>/dev/null
else
stat --format "%n;%Y;%X;%U" "$YOUNGEST_FILE" >> $DATA_RAW_FILE_LIST 2>/dev/null
fi
else
stat --format "%n;%Y;%X;%U" "$i" >> $DATA_RAW_FILE_LIST 2>/dev/null
fi
done
The script above, option 2 & 3 will work fine but option 1 show nothing. Then I try another way by adding another variable into the script and it did not work as well. Can someone show me how to make it read my file system /data?
echo "Please select from below:"
cat<<EOF
1. /data
2. /data01
3. DO_ALL_DATA_AT_ONCE
Press ENTER to exit
EOF
echo -n "Selection:"
read ans
if [[ $ans == "" ]] ; then
echo "Bye"
exit 0
fi
case $ans in
"1"|"2"|"3")
FS_TARGET="$ans"
;;
*)
echo "Unknown option - quitting"
echo "Bye"
exit 0
;;
esac
DATA="/data0${FS_TARGET}/*"
if [[ $FS_TARGET -eq 1 ]] ; then
#if the selection is 1, then change do the variable....
DATA="/data/*"
fi
if [[ $FS_TARGET -eq 3 ]] ; then
#if the selection is 3, then we just do everything, in one go
DATA="/data/* /data01/*"
fi
I want to check for file in directory if there then push it to ssh server checing server connection if file not there then try 3 times with each 1min interval and in between if it comes ( on 2nd attend for example) then try again to connect ssh and push. else check for 3 attempts and exit
Please check my below code it is halting after 1st attempt ( during 2nd attempt I am making file available)
#!/bin/sh
echo "OK, start pushing the Userdetails to COUPA now..."
cd /usr/App/ss/outbound/usrdtl/
n=0
until [ $n -ge 3 ] || [ ! -f /usr/App/ss/outbound/usrdtl/USERS_APPROVERS_*.csv ]
do
if [ -f /usr/App/ss/outbound/usrdtl/USERS_APPROVERS_*.csv ] ;
then
pushFiles()
else
n=$[$n+1]
sleep 60
echo " trying " $n "times "
fi
done
pushFiles()
{
echo "File present Now try SSH connection"
while [ $? -eq 0 ];
do
echo $(date);
scpg3 -v /usr/App/ss/outbound/usrdtl/USERS_APPROVERS_*.csv <sshHost>:/Incoming/Users/
if [ $? -eq 0 ]; then
echo "Successfull"
echo $(date);
echo "Successfull" >> /usr/App/ss/UserApproverDetails.log
exit 1;
else
echo $(date);
echo "Failed" >> /usr/App/ss/UserApproverDetails.log
echo "trying again to push file.."
scpg3 -v /usr/App/sg/outbound/usrdtl/USERS_APPROVERS_*.csv <ssh Host>:/Incoming/Users/
echo $(date);
exit 1;
fi
done
}
I've tried to simplify this code for you. I hope it helps:
#!/bin/bash
outdir="/usr/App/ss/outbound/usrdtl"
logfile="/usr/App/ss/UserApproverDetails.log"
file_prefix="USERS_APPROVERS_"
function push_files() {
echo "File present now try SSH connection"
local attempts=1
local retries=2
date
while [[ ${attempts} -lt ${retries} ]]; do
if scp ${outdir}/${file_prefix}*.csv <sshHost>:/Incoming/Users/ ; then
echo "Successful" | tee -a ${logfile}
date
exit 0
else
echo "Failed" >> ${logfile}
fi
attempts=$((attempts+1))
do
echo "scp failed twice" | tee -a ${logfile}
exit 2
}
echo "OK, start pushing the Userdetails to COUPA now..."
cd ${outdir}
attempts=1
retries=3
while [[ ${attempts} -lt ${retries} ]]; do
echo "looking for files...attempt ${attempts}"
if test -n "$(shopt -s nullglob; echo ${outdir}/${file_prefix}*.csv)"; then
push_files()
fi
attempts=$((attempts+1))
sleep 60
done
echo "Files were never found" | tee -a ${logfile}
exit 1
Look at this code and tell me how it's not doing what you're trying to do. The most complicated part here is the nullglob stuff, which is a handy trick to see if any file in a glob matches
Also, I generally used bashisms.
I am trying to check if a user types multiple arguments in a command line using case and if/else statements. What's wrong is that I keep getting the default case instead of the same command, but with 2 more arguments. For instance, one of my conditions is:
del)
if [ -z "$2" ] || [ -z "$3" ]
then
echo "Usage: removes a file"
else
echo "using Bash command: rm $2 $3"
rm $2 $3
echo done
fi
prints the first condition, but if I type, say, del aaa bbb, I get the default case, which is:
echo "ERROR: Unrecognized command"
I'm also using this to read a user's input, if that helps.
read -p "wcl> " -r wcl $2 $3
I don't really know if there's a better way to solve this without scrapping all my code and starting from scratch.
This is the full code:
#!/bin/bash
#use read command
echo Welcome to the Windows Command Line simulator!
echo Enter your commands below
while true
do
read -p "wcl> " -r wcl $2 $3
case $wcl in
dir)
echo "using Bash command: ls $2 $3"
ls
continue
;;
copy)
FILE="$2"
if [ "$#" -ne 3 ]
then
echo "Usage: copy sourcefile destinationfile"
else
echo "using Bash command: cp $2 $3"
if [ -f "$FILE" ]
then
cp $2 $3
else
echo "cannot stat $FILE: No such file or directory">&2
fi
echo done
fi
continue
;;
del)
if [ -z "$2" ] || [ -z "$3" ]
then
echo "Usage: removes a file"
else
echo "using Bash command: rm $2 $3"
rm $2 $3
echo done
fi
continue
;;
move)
if [ -z "$2" ] || [ -z "$3" ]
then
echo "Usage: moves a file to another file name and location"
else
echo "using Bash command: mv $2 $3"
mv $2 $3
echo done
fi
continue
;;
rename)
if [ -z "$2" ] || [ -z "$3" ]
then
echo "Usage: renames a file"
else
echo "using Bash command: mv $2 $3"
mv $2 $3
echo done
fi
continue
;;
ipconfig)
ifconfig eth0 | grep "inet addr" | cut -d ':' -f 2 | cut -d ' ' -f 1
continue
;;
exit)
echo "Goodbye"
exit 1
;;
^c)
echo "Goodbye"
exit 1
;;
*)
echo "ERROR: Unrecognized command"
continue
esac
done
You can't use read to set the positional parameters, although it isn't clear why you would need to here. Just use regular parameters.
while true
do
read -p "wcl> " -r wcl arg1 arg2
case $wcl in
dir)
echo "using Bash command: ls $arg1 $arg2"
ls "$arg1" "$arg2"
continue
;;
# ...
esac
done
The way read -r wcl $2 $3 is executed is that $2 and $3 are first expanded to give names that read will use to set variables. If those aren't set, then the command reduces to read -r wcl, and so your entire command line is assigned to the variable wcl, not just the command.
However, read by itself is not going to do the same parsing that the shell already does, if you goal is to write your own shell.
If you are really using bash, you can insert the words you read into positional parameters through an array. (You could also just leave them in the array, but the syntax for referring to positional parameters is simpler.)
# -a: read the successive words into an array
read -r -p "wcl> " -a params
# set the positional parameters to the expansion of the array
set -- "${params[#]}"
wcl=$1 # Or you could do the case on "$1"
This will also set $# to the number of words read, as a side-effect of setting the positional parameters.
As #chepner points outs, the read is problematic: It simply splits the input into whitespace-separated words, without respecting quotes, backslashes, and whatever other shell metacharacters you might want to implement. Doing a full bash-style parse of a command-line in bash itself would be quite a difficult exercise.
So, the idea is to have a script that tries to run a command, and if the command fails it shows up any warnings/errors. My try:
$ cat try.sh
#! /bin/sh
tempfile=`tempfile 2>/dev/null` || tempfile=/tmp/temp$$
trap 'rm -f $tempfile >/dev/null 2>&1' 0
trap 'exit 2' 1 2 3 15
echo "$#"
if ! "$#" >$tempfile 2>&1; then
cat $tempfile;
false;
fi
Do you think that this script is ok (wrt portability and functionality)?
Some changes I would make:
Use "$#" as Steve Emmerson suggested
Don't redirect stdout of tempfile to /dev/null; that's what you're trying to capture in the variable!
Consider mktemp; it is more portable.
Capture and exit with actual exit code of command, so information is not lost.
E.g., without error checks,
tempfile=`mktemp 2>/dev/null || echo /tmp/tempfile$$`
[ -w "$tempfile" ] || { echo "Can't make tempfile" >&2; exit 1; }
"$#" 2> $tempfile
rc=$?
case $rc in
0) ;;
*) cat "$tempfile" >&2 ;;
esac
rm -f "$tempfile"
exit $rc
I would enclose the $# in double quotes in the "if" statement in order to preserve word boundaries.