LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Linux - Software (https://www.linuxquestions.org/questions/linux-software-2/)
-   -   Issue in shell scripting (https://www.linuxquestions.org/questions/linux-software-2/issue-in-shell-scripting-4175534222/)

arunkumar26 02-16-2015 04:02 PM

Issue in shell scripting
 
The below coding is to find who are all the user having home directory
for (( a=1; a<=$#; a++ ))
do
ls /home | grep $$a >>/dev/null
if [[ $? -eq 0 ]]
then
echo $$a is having home dir
else
echo $$a is not having home dir
fi
done
Note: Here i am trying to pass usernames as a parameter.
Output:
6712a is having home dir
Issue:
$$a is not taking my parameter value. it is taking $$ as a process ID (6712). I want to check $1, $2, $3, etc using $$a. I tried by using brace like $($a), ${$a}, etc. But it is not working. Kindly help me to resolve this issue.

Ser Olmy 02-16-2015 04:54 PM

It took me a while to understand what you were trying to accomplish with that for loop, but I think I get it now: You want the loop to iterate though the command line parameters, and then refer to a specific parameter variable, say "$1", inside the loop using a syntax like $${a}, am I right?

The problem is related to the way parameters are expanded. You want "$${a}" to expand to something like "$1" or "$2" initially, with 1 or 2 being the value of $a, and then you want the shell to expand the actual parameter variable ($1 or $2, depending on the value of $a). As you've seen, that's doesn't work.

While some clever coding using delayed expansion might work, may a suggest an alternative approach? How about focusing on $1 and then use shift to iterate through the command line arguments? Just create a loop that runs while $1 is non-null, and inside that loop, check for the existence of a home directory and run shift at the end:
Code:

while ! [ "$1" == "" ]; do
  if [ -d /home/$1 ]; then
    echo $1 has a home directory
  else
    echo $1 does not have a home directory
  fi
  shift
done

(I replaced your "ls dir, pipe to grep, check exit code" test with if [ -d dir ], as that's much less likely to return a false positive.)

This way, you'll be able to handle any number of command line arguments, which I'd argue is an improvement on your initial approach which may be limited to 9, depending on which shell you're using.

wpeckham 02-16-2015 05:53 PM

Picking at nits, again...
 
It may be worthwhile to note that this only finds users with home folders directly under /home.
I run systems where some sets of users may have home folders in different locations.
Were that an issue, parsing /etc/passwd for accounts with value UID in the user range, and detecting the home folder field, and testing its validity and existance would be a more rigerous solution.

That may be far beyond the need here, just thought I should mention it.
And that even that method could be 'fooled' by some unexpected non-user accounts in high UID space.

arunkumar26 02-17-2015 12:04 PM

Quote:

Originally Posted by Ser Olmy (Post 5318348)
It took me a while to understand what you were trying to accomplish with that for loop, but I think I get it now: You want the loop to iterate though the command line parameters, and then refer to a specific parameter variable, say "$1", inside the loop using a syntax like $${a}, am I right?

The problem is related to the way parameters are expanded. You want "$${a}" to expand to something like "$1" or "$2" initially, with 1 or 2 being the value of $a, and then you want the shell to expand the actual parameter variable ($1 or $2, depending on the value of $a). As you've seen, that's doesn't work.

While some clever coding using delayed expansion might work, may a suggest an alternative approach? How about focusing on $1 and then use shift to iterate through the command line arguments? Just create a loop that runs while $1 is non-null, and inside that loop, check for the existence of a home directory and run shift at the end:
Code:

while ! [ "$1" == "" ]; do
  if [ -d /home/$1 ]; then
    echo $1 has a home directory
  else
    echo $1 does not have a home directory
  fi
  shift
done

(I replaced your "ls dir, pipe to grep, check exit code" test with if [ -d dir ], as that's much less likely to return a false positive.)

This way, you'll be able to handle any number of command line arguments, which I'd argue is an improvement on your initial approach which may be limited to 9, depending on which shell you're using.

Thank you for your reply. The above coding is to get single username as a parameter. If i want to pass muliple username as a parameter, is there any other possible way without using $$a??
Due to the use of $$, the output is showing me PID.
Actual output:
$$a--> {$$}a --->{6712}a ----> 6712a ---> showing PID plus 'a'
Expecting output
$$a---> ${$a} --->${1} ---> $1 ---> 1st parameter value
it would be helpful for me, if you let me know the proper syntax to get above result.
Thank you!!

arunkumar26 02-17-2015 12:13 PM

Quote:

Originally Posted by wpeckham (Post 5318385)
It may be worthwhile to note that this only finds users with home folders directly under /home.
I run systems where some sets of users may have home folders in different locations.
Were that an issue, parsing /etc/passwd for accounts with value UID in the user range, and detecting the home folder field, and testing its validity and existance would be a more rigerous solution.

That may be far beyond the need here, just thought I should mention it.
And that even that method could be 'fooled' by some unexpected non-user accounts in high UID space.

Yes. it is great idea. I understand that home directory may present in different location too. Grepping home directory from /etc/passwd is a best way.

Ser Olmy 02-17-2015 06:25 PM

Quote:

Originally Posted by arunkumar26 (Post 5318851)
Thank you for your reply. The above coding is to get single username as a parameter. If i want to pass muliple username as a parameter, is there any other possible way without using $$a??

Did you try the code I posted? It does support multiple user accounts as arguments. In fact, it supports any number of arguments, unlike your approach which may be limited to 9 arguments ($1 - $9).
Quote:

Originally Posted by arunkumar26 (Post 5318851)
Due to the use of $$, the output is showing me PID.
Actual output:
$$a--> {$$}a --->{6712}a ----> 6712a ---> showing PID plus 'a'
Expecting output
$$a---> ${$a} --->${1} ---> $1 ---> 1st parameter value
it would be helpful for me, if you let me know the proper syntax to get above result.

I attempted to explain why this isn't working: You're expecting the shell to perform variable expansion twice, once from "$${a}" to "$1" (or whichever number $a may contain), and then again from "$1" to the contents of the $1 variable. The shell just doesn't expand variables recursively like that.

You can code around it with eval if you like:

eval echo \$${a} ---> eval echo $1 ---> echo (1st parameter value)

The first expansion returns a literal $ character (due to the backslash escape) followed by the contents of the variable $a. Then echo $1 is evaluated, returning the contents of $1.

In general, using eval is not a recommended practice since it basically just runs whatever you pass it. If you make a simple mistake or a malicious user is able to manipulate the input to embed a command, you may end up executing said command and compromise the system. Anyway, here's how it might work:
Code:

for (( a=1; a<=$#; a++ )) do
  tempvar=\$$a
  user_acct=$(eval echo $tempvar)
  if [ -d /home/$user_acct ]; then
    echo $user_acct has a home directory
  else
    echo $user_acct does not have a home directory
  fi
done

(Thought experiment: Imagine what would happen in the above script if the $1 variable somehow contained the text "foo ; dd if=/dev/zero of=/dev/sda". eval is potentially very dangerous.)

As for locating a user's home directory by searching through /etc/passwd, that may not be such a good idea. It will work on most standalone systems, but will fail if the user account is stored in a database.

A (much) better approach would be to parse the output from getent passwd. It returns user information in exactly the same format as used by the /etc/passwd file, but it fetches that information from whichever source the system is configured to use, be that /etc/passwd, NIS, Active Directory/winbind, an LDAP server, or something else entirely.

Here's a version of my code using getent:
Code:

while ! [ "$1" == "" ]; do
  home_dir=$(getent passwd | grep ^$1\: | cut -d : -f 6)
  if [ ! "$home_dir" == "" ] && [ -d $home_dir ] ; then
    echo $1 has a home directory
  else
    echo $1 does not have a home directory
  fi
  shift
done

And here's a (horribly insecure) version that expands variables recursively using eval:
Code:

for (( a=1; a<=$#; a++ )) do
  tempvar=\$$a
  user_acct=$(eval echo $tempvar)
  home_dir=$(getent passwd | grep ^$user_acct\: | cut -d : -f 6)
  if [ ! "$home_dir" == "" ] && [ -d $home_dir ]; then
    echo $user_acct has a home directory
  else
    echo $user_acct does not have a home directory
  fi
done

Note that since grepping the output from the getent command (or the /etc/passwd file for that matter) will return an empty string if the user either doesn't exist or doesn't have a defined home directory, an extra test is required to check for a blank directory variable. For some reason, if [ -d $var ] evaluates to "true" if $var is blank.

wpeckham 02-17-2015 09:09 PM

nice!
 
Listen to Ser Olmy, he has excellent taste.
I was wondering about that use of grep though, I think another solution may serve better: using the above solution without eval but in my style.

Code:

while [[ -n "$1" ]]; do
  if id ${1} >/dev/null 2>&1
  then
    home_dir=$(getent passwd $1 | cut -d: -f6)
    if [[ -n "$home_dir" ]] && [[ -d $home_dir ]] ; then
        echo "${1} has a home directory ${home_dir}"
    else
        echo "${1} does not have a home directory."
    fi
  else
    echo "${1} is not a user"
  fi
  shift
done

Since getent with a parameter only outputs for that parameter, the disk access become avoidable.
I added a test for real users simply because I cannot leave well enough alone. Ever. And Ser Olmy may know a better way.

Keith Hedger 02-18-2015 08:26 AM

To use the value of a bash variable to point to another variable you don't need to use eval you can do sort of 'c' type pointer like so:
Code:

AVAR="some text"
ANOTHERVAR="more text"
POINTERVAR="AVAR"
echo $AVAR
some text
echo $POINTERVAR
AVAR
echo ${!POINTERVAR}
some text
POINTERVAR="ANOTHERVAR"
echo ${!POINTERVAR}
more text

This is read only you can't asign values with "!" but it does avoid at least one use of eval, this is not POSIX complient ( I think ) so if you need to use this with a different shell than BASH you will have to check that it works on that shell.


All times are GMT -5. The time now is 11:08 PM.