Search code examples
bashescapingline-breaksdouble-quotessingle-quotes

Escaping many layers of cascaded single and double quotes in osx bash


I use a Mac (osx sierra) and I have been learning how to use the bash. I am trying new stuff as I go along to grasp some concepts. This time I was experimenting with functions and aliases. But then I couldn't wrap my head around this problem:

I first echoed this function into the .profile to insert new aliases and functions easily into the .profile file.

function editprofile(){
echo "$@" >> ~/.profile
}

This function worked very well for some alias insertions. But after I tried to insert an alias for the script (the script below) that makes my mac sleep, I realized that the function creates some diffuculties with the cascading single and double quotes. I believe this will be the case for most scripts that uses lots of layers of single and double quotes.

osascript -e 'tell application "Finder" to sleep' && exit

The code below is me trying to use my function to insert the code above as an alias into the .profile.

editprofile 'alias _sleep=' "'" 'osascript -e' "'" 'tell application "Finder" to sleep' "'" '&& exit' "'"

The problem is that when the second script is echoed into the .profile file, I should still keep some escape characters, otherwise the code is interpretted by the bash incorrectly. I think this would also be the case with may other scripts that have this many layers of quotes, so I though I should ask if there is any way around.

P.S. On a related note, it seems like when I type this:

function editprofile(){echo "$@" >> ~/.profile}

instead of this:

function editprofile(){
echo "$@" >> ~/.profile
}

into the .profile file, the script doesn't work. Is it because of the line breaks?


Solution

  • Assuming that you have a function, as opposed to an alias:

    _sleep() { osascript -e 'tell application "Finder" to sleep' && exit; }
    

    ...you can emit its text with declare -f. Thus:

    declare -f _sleep >>~/.profile
    

    ...or, to use your existing editprofile function:

    editprofile "$(declare -f _sleep)"
    

    The easiest approach is just that: Define the function in your local shell, then have the shell itself do the work of emitting it -- and quote that emitted content so it doesn't get field-split into individual arguments (and then have those arguments individually evaluated as globs).


    If you don't want to go that route, there are approaches available; they're just varying degrees of unpleasant.

    printf -v cmd_var '%q ' osascript -e 'tell application "Finder" to sleep' 
    

    ...will put correctly-quoted contents into "$cmd_var". You could then:

    printf -v sleep_def '_sleep() { %s && exit; }' "$cmd_var"
    

    ...which will give you a function declaration in sleep_dev that can be evaled to execute it locally, or appended to your .profile, &c.

    editprofile "$sleep_def"
    

    ...will behave appropriately in that context.