linuxbashshell

What does \$$1 mean in this shell function?


I saw this function definition from "Linux Shell Scripting Cookbook", I wonder why there needs two $ sign is needed in the last part of eval $1=\"$2\$\{$1:+':'\$$1\}\". How to understand each of the $ sign in entire eval condition? Thanks!

prepend() { [ -d "$2" ] && eval $1=\"$2\$\{$1:+':'\$$1\}\" && export $1 ; }

Solution

  • In addition to @tripleee's correct answer, I want to suggest you to try this at command line:

    set -- foo bar
    echo $1=\"$2\$\{$1:+':'\$$1\}\"
    
    foo="bar${foo:+:$foo}"
    

    Alternatively

    As eval is evil, here is an alternative (but care evil is in details read further!):

    prepend() {
        [[ -d $2 ]] &&
            printf -v "$1" '%s' "$2" "${!1+:}" "${!1}" &&
            export "$1"
    }
    

    which could be written as oneliner:

    prepend() { [[ -d $2 ]]&&printf -v "$1" '%s' "$2" "${!1+:}" "${!1}"&&export "$1";}
    

    Tests:

    mkdir /tmp/foo /tmp/bar
    prepend myVar /tmp/foo 
    echo ${myVar@A}
    
    declare -x myVar='/tmp/foo'
    
    prepend myVar /tmp/bar 
    echo ${myVar@A}
    
    declare -x myVar='/tmp/bar:/tmp/foo'
    
    prepend myVar /tmp/baz
    echo ${myVar@A}
    
    declare -x myVar='/tmp/bar:/tmp/foo'
    

    Sanitizing:

    Regarding @jhnc's comment, you could want to sanitize submitted variable name:

    Let's try (again):

    prepend 'h[`date +%c.%N>/dev/tty`]' /tmp/foo
    
    Mon Nov 25 08:04:26 2024.549285838
    Mon Nov 25 08:04:26 2024.552602686
    Mon Nov 25 08:04:26 2024.554057179
    bash: export: `h[`date +%c.%N>/dev/tty`]': not a valid identifier
    

    Where date command was exectuted by prepand function (3 times)!!

    To avoid this you may add a little test if stringContain ... in your command:

    prepend() {
        case $1 in
            *[\`\$]* )
                return 1
            ;;
        esac
        [[ -d $2 ]] &&
            printf -v "$1" '%s' "$2" "${!1+:}" "${!1}" &&
            export "$1"
    }
    

    In one line:

    prepend() { case $1 in *[\`\$]* ) return 1;;esac;[[ -d $2 ]]&&printf -v "$1" '%s' "$2" "${!1+:}" "${!1}"&&export "$1";}
    

    Or two lines:

    prepend() { case $1 in *[\`\$]* ) return 1;;esac
      [[ -d $2 ]] && printf -v "$1" '%s' "$2" "${!1+:}" "${!1}" && export "$1" ;}
    

    Then

    unset myVar
    prepend myVar /tmp/foo 
    echo ${myVar@A}
    
    declare -x myVar='/tmp/foo'
    
    prepend myVar /tmp/bar 
    echo ${myVar@A}
    
    declare -x myVar='/tmp/bar:/tmp/foo'
    
    prepend myVar /tmp/baz
    echo ${myVar@A}
    
    declare -x myVar='/tmp/bar:/tmp/foo'
    

    Upto there, everything look fine. Then finally:

    prepend 'h[`date>/dev/tty`]' /tmp/foo
    
    prepend 'h[$(date>/dev/tty)]' /tmp/foo
    

    Nothing! but:

    echo $?
    
    1