Simple bash scripting question

Hafiz
New Contributor II

Guys how do I remotely mount network shares that contain people's usernames but do it in a bash script i.e. matches the following variable:
$ echo $USER

I have tried the following:
$ myusername=$USER
$ open 'cifs://Some_Remote_Network_Share/$myusername$'

The above does not pick up the myusername variable and nothing else that I have tried does either. Sorry I know it's a stupid question but there must be a way to pass in the $USER variable !?!

Also, I think I can put the script into Casper Self Service but I was also thinking of making the bash script into an app which can be put on the dock, seems Automator can do that:
http://stackoverflow.com/questions/281372/executing-shell-scripts-from-the-os-x-dock

For those that have converted Bash scripts into "dockable" applications is Automator the best way to go?

Thanks again!

13 REPLIES 13

mm2270
Legendary Contributor III

Try enclosing the variable containing the username in curly brackets { } like this:

open 'cifs://Some_Remote_Network_Share/${myusername}$'

Its possible the variable isn't being recognized because its being placed adjacent to other characters in the command line. Placing curly brackets around it prevents the shell from combining it with other characters when its read, which makes it an invalid reference.
I also don't think you need to escape the $ sign, but I'm not certain on that. Try it both ways I suppose, if the former doesn't work.

As for using Automator to make double clickable script apps, its an option, but my preference is Platypus.

Lastly, I would search around on here, because there are better approaches to what you're trying to do. This is a fairly common need and others already have scripts and process that do this you can pick up ideas from or just use as is.

jyergatian
Contributor

I have something like this available in Self Service for a client mounting DFS. It seems to work well.

#!/bin/bash
su - `ls -l /dev/console | awk '{print $3}'` -c
osascript <<EOD
set short_name to system attribute "USER"
tell application "Finder"
    mount volume "cifs://Some_Remote_Netword_Share/" & short_name & ""
end tell
EOD

davidacland
Honored Contributor II
Honored Contributor II

The single quote marks would have an issue as they can't expand the variables. For that part you could try double quotes instead.

If you're not sure what its doing, try adding a echo $VARIABLENAME after each line or variable to get some logging and info back. The echos will show up in the policy log in Casper.

I normally use AppleScript to make clickable applications using the do shell script "/path/to/shell/scrpt" command.

Last thought, it might be worth putting the script into Casper, creating a policy with a custom trigger and then having the client call the policy with jamf policy -event trigger_name. That way you can keep editing the script in the JSS.

jarednichols
Honored Contributor

You can also use the JAMF binary to mount shares.

Hafiz
New Contributor II

Thanks everyone especially @mm2270 and @davidacland Unfortunately Platypus doesn't accept user input and I can't seem to be able to get Automator to accept user input but I think it should be handle the script below (any further ideas for getting it to be a dockable app and accepting user input several times?). Basically, Automator somehow has to be able to handle user inputs of y, y, y, and y as the mount points get added in. Thanks!

!/bin/bash

Mount a network share

Assign username of logged in user to a variable

myusername=$USER
echo
echo "Hello! Which of the following network shares would you like to mount today:"
echo

Public Software

read -p "1. Public Software <y/N>" public_software
if [[ $public_software == "y" || $public_software == "Y" || $public_software == "yes" || $public_software == "Yes" || $public_software == "YES" ]]
then
open 'cifs://nas-xxx/PublicSoftware'
fi
echo

Network Drive

read -p "2. Network Drive <y/N>" network_drive
if [[ $network_drive == "y" || $network_drive == "Y" || $network_drive == "yes" || $network_drive == "Yes" || $network_drive == "YES" ]]
then
open 'cifs://xxxxxxxxxxxxx002/networkdrive'
fi
echo

Personal Strategies

read -p "3. Personal Strategies <y/N>" personal_strategies
if [[ $personal_strategies == "y" || $personal_strategies == "Y" || $personal_strategies == "yes" || $personal_strategies == "Yes" || $personal_strategies == "YES" ]]
then
open 'cifs://xxxxxxxxxxx001/PersonalStrategies$'
fi
echo

Home Folder

read -p "4. Home Drive Folder (for your personal storage) <y/N>" home_folder
if [[ $home_folder == "y" || $home_folder == "Y" || $home_folder == "yes" || $home_folder == "Yes" || $home_folder == "YES" ]]
then
open "cifs://xxxxxxxxx001/${myusername}$"
fi
echo

mm2270
Legendary Contributor III

@Hafiz You never mentioned anything about user input in your OP. Diff story then. Though its possible to use Platypus with other tools bundled in (like cocoaDialog) to make double clickable apps that can ask for input, if your need here is simple enough, stick with straight Applescript if you can and make it into an .app bundle.

If you aren't able to flip this all into an Applescript, as I mentioned, you can make apps with Platypus that can use cocoaDialog as the GUI element, and Platypus is just the wrapper that makes it all into an .app someone can double click.

If you want to go that route, I can provide pointers. I've built several apps using this combination that work quite well.

Edit: BTW, I just looked at your script posted above, and there's no way you can make that kind of script work to ask for input in a GUI fashion. The 'read' command is intended to be used in a shell environment. Automator or other tools are not going to be able to use that to prompt the user for any input unless you are providing some kind of console for them to type into, which is both difficult and ugly.

Hafiz
New Contributor II

@mm2270 Drat! Thanks for letting me know the futility of making this into a GUI script because of the read command. Yes, okay some pointers for making Platypus with cocoaDialog into a double clickable app with user input would be useful and fun for the future (probably useful for other people as well).

mm2270
Legendary Contributor III

@Hafiz Would something that pops up a dialog like the following work for your users? So they can just check the items they want mounted?

9546c8cc031a4e4c9d7c543651af340f

davidacland
Honored Contributor II
Honored Contributor II

I've been trying to think of the other app I used all day and just remembered! Pashua lets you easily make scripts that have a GUI interface.

Looks like @mm2270 has already offered a pretty neat solution but I just wanted to get pashua out of my head!

mm2270
Legendary Contributor III

@Hafiz Are you still needing help with this? Any interest in what I posted above? If so, just post back and I can show you the details. I'm not going to bother writing up a long post if this isn't something you're looking for.

Hafiz
New Contributor II

@mm2270 Yes, I am still interested in creating a dockable app for all users that will give them network shares that they can choose to mount and a short description of the share itself. Done graphically and deployed out to all the MacBook users it should prove to be quite useful.

mm2270
Legendary Contributor III

Hi @Hafiz Ok, I have some time now to write this up. Here is a little tutorial on how to build an app with Platypus that uses cocoaDialog for parts or all of the UI.

First thing I wanted to post was the script I generated that will do the actual work. This is what created the dialog image I show above.

#!/bin/bash

####################### Variables #######################

## Get the logged in user's name
loggedInUser=$(stat -f%Su /dev/console)

## Get the script's working directory
workingDir="$( pwd )"

## Generate the path to the app's embedded cocoaDialog
cdPath="${workingDir}/cocoaDialog.app/Contents/MacOS/cocoaDialog"

## Path to the sharepoint icon, used in the dialog
networkIcon="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericFileServerIcon.icns"

##################### End variables #####################


##################### Static Arrays #####################

## Three arrays:
## One for the human readable name for the dialog
## One for the mount points
## One for the server names

ShareNames=("Public Software" "Network Drive" "Personal Strategies" "Home Drive Folder")
Shares=("PublicSoftware" "networkdrive" "PersonalStrategies" "${loggedInUser}")
## Edit this list to match the actual server names
Servers=("nas-xxx" "xxxxxxxxxxxxx002" "xxxxxxxxxxx001" "xxxxxxxxx001")

################### End Static Arrays ###################


## This is the function that does the actual mounts of the sharepoints selected by the user
function mountShares ()
{

## Loop over the SelectedShares array,
## mounting each share using its corresponding "Servers" path and the share name
x=0
while read shareName; do
    open "cifs://${ServerPaths[$x]}/${shareName}$"
    let x=$((x+1))
done < <(printf '%s
' "${SelectedShares[@]}")

exit 0

}


#################### Start of script ####################

## Create a cocoaDialog checkbox dialog asking the user to select from a list of shares to mount
RequestDialog=$("$cdPath" checkbox 
    --title "" 
    --items "${ShareNames[@]}" 
    --label "Hello! Which of the following network shares would you like to mount today:" 
    --button1 "Connect" 
    --button2 " Cancel " 
    --cancel "button2" 
    --width 400 
    --icon-file "$networkIcon" 
    --value-required 
    --empty-text "Please check at least one item before clicking Connect. Or click Cancel to exit." 
    --quiet)


## Since we are using the 'value-required' flag, the user must check at least one item, or cancel the dialog.
## We are also using the 'quiet' flag which suppresses the button clicked. Therefore, if the command returned any values,
## the user must have checked some items and clicked "Connect". If so, proceed with reading the checked items values.

if [ ! -z "$RequestDialog" ]; then

    ## Create array of checked items to loop over
    while read box; do
        CheckedItems+=($box)
    done < <(printf '%s
' "$RequestDialog")

    ## Loop over the CheckedItems array, creating 2 new arrays,
    ## for SelectedShares (share names), and ServerPath
    x=0
    while read item; do
        if [ "$item" == "1" ]; then
            SelectedShares+=("${Shares[$x]}")
            ServerPaths+=("${Servers[$x]}")
        fi
        let x=$((x+1))
    done < <(printf '%s
' "${CheckedItems[@]}")

    ## If we got this far, move on to the mountShares function to actually mount them.
    mountShares

else
    ## if we got an empty result back from the dialog, the user canceled, so exit
    echo "User canceled. Exiting..."
    exit 1
fi

Now, there are a few things (some semi advanced) going on in the script, so I should explain it a bit. I put comments into the script, but just so its clear what's happening...

In the Variables section, I'm setting a couple of items.
loggedInUser - This just gets the logged in user name. About 10 different variations or ways to do this. This is just one. It pulls the short name of the logged in account
workingDir - This is an important one. So, Platypus always runs the script from the .apps "Resources" directory. We can take advantage of this fact since any bundles tools, like cocoaDialog, also go into the Resources directory. So this is just getting the script's working directory, so it knows where to find cocoaDialog. This allows the final .app to run from literally any location. The Desktop, the Applications folder, off a thumb drive, wherever.
cdPath - Here I'm using the workingDir path we got above to tell the script where the full path to the cocoaDialog binary is. Again, this allows it to run self contained from any location.
networkIcon - Nothing special here. Just a hardcoded path to the GenericFileServerIcon .icns file we'll use in the dialog. Note that its also possible to copy this icon or even a custom one into the Resources folder and use the same trick as with cdPath to generate a path to the icon. I've done that before to ensure the icon will always appear correctly.

In the Static Arrays section, I'm setting 3 static bash arrays, which are special kinds of lists, in case you haven't used those before.
ShareNames - These are the share names we'll use in the dialog. These can be labeled whatever you want, including having some descriptions after them if need be.
Shares - These are the actual share names as they exist on the server, so these need to be exact as they'd be called in the open command later.
Servers - Like above, these are the server names the shares come from.
The only important thing about these arrays is that a) They need to be the same qty across them all, so in this case, 4, and b) They have to be in the same order, so "Public Software" matches up with "PublicSoftware" and "nas-xxx" as the first item respectively. If they are out of order or not the same quantity, the script won't work right.

The next item is a function called mountShares. I'll come back to that since it may not make complete sense what's happening until you go thru the rest of the script.

If you go down to Start of Script, I have a variable I'm creating using a cocoaDialog call. The dialog uses the checkbox style. I'm passing the ShareNames array to the --items flag, which sets each share name as its own checkbox. There's some dialog text, 2 buttons, one designated as the 'cancel' button, a width setting and the network icon.
The last 3 items in the cocoaDialog call are important to understand. The --value-required flag tells cocoaDialog not to exit with the default button ("Connect" in this case), when asking for user input (checkboxes, radio buttons, text fields, etc) The user must do something or select something before they can click the "Connect" button. If they don't, they get the text that shows up after the --empty-text flag.
The --cancel flag we set earlier allows them to click Cancel to exit the script if they want.
Last is the --quiet flag. This suppresses the button clicked callback. Normally we'd see a "1" or "2" depending on the button clicked. Since we're forcing either a selection or a cancel from the user, we only need to check to see if the variable got filled with some result. If it comes back null, then they canceled.
That's what we're doing next in the script. Theif [ ! -z "$RequestDialog" ]; then basically says, if RequestDialog isn't empty, then the user checked at least one box, so let's continue!
The next item (while read box; do) is a loop that converts the string of 0s and 1 from the checkboxes returned into an array I can process later.
The while read item; do loop now takes the array I just built and checks each one to see if we see a "1" which means checked. For each "1" we build 2 more arrays, for the SelectedShares and ServerPaths This is using the array index or indices, basically the $x is the current index, starting from 0 and moves up 1 with each loop, so it knows which strings to pass back to the arrays as it builds them.

Finally, now that we have a few arrays we can use, we move on to the mountShares function from above. Here, I'm just looping over the SelectedShares array and for each share, setting the corresponding ServerPath and using the open smb:// something something command to open each one for the user. Then it exits.

What's not happening in this script at all, but should, is checking for a network connection. Since your users can run this from their Mac and not just from something like Self Service, it would be a good idea before throwing up a dialog, to check for the network so it can mount the shares. Maybe a simple ping to a primary domain controller or some other internal server that should always be available for example. I'll leave that up to you to determine. The script as is, won't account for situations where it can't reach the network, so it'll just exit with nothing mounting if it can't reach any of them.

So hopefully that all makes sense what's happening in the script and will give you some ideas.

Now, for building this into an actual app,

  • Open Platypus and start a new project if need be.
  • Make sure you have a copy of the last beta 3.0 version of cocoaDialog from the github page. The 2.x versions did not have the checkbox style so you have to use that last beta build.
  • Drag the cocoaDialog.app into the field at the bottom where it says "Files to be bundled into the application's Resources folder."
  • Make sure to select the shell script like above once you've saved it using something like TextWrangler or made sure its executable into the Script path field.
  • Name the app what you want and set a bundle identifier if you want something custom.
  • For the Output pop up menu, my recommendation is to choose "None", but you can pick one of the other ones, like Progress bar or Text Window. The ones below Text Window won't make sense to use since they are for different purposes.
  • I recommend unchecking the "Remain running after initial execution" box. This makes sure after the script runs, gets input and mounts the shares that the app quits, ready for the next time its run.

Oh, there is a bug that prevents the icon you drag into that image well from being applied when you build the app using the GUI. So don't bother to put a custom icon there. You can copy/paste a custom icon onto the final app using the Finder and it sticks.

Give all that a try and let me know if you run into any trouble.

BTW, if you want to first test the script, you can create a new cdPath variable with a hardcoded path to cocoaDialog on your Mac for testing purposes and just comment out the other one. That's generally what I do. Once I have it all working, I remove the hardcoded path and uncomment the one using $workingDir as the start of its path and build the app.

Hafiz
New Contributor II

@mm2270 Thank you so much! I am sure others will find this useful too. I will try this out next week and let you know how I get on. Cheers!