Simple bash argument parser











up vote
2
down vote

favorite












In a bash script, I need to parse arguments that have the following form:




  1. The main arguments can be thought of as being a single argument, but I do not want to force users to quote the entire thing, so when the argument contains spaces I must be able to handle multiple arguments.


  2. One flag may be passed as -<flag> where <flag> can be an arbitrary word (without spaces)


  3. Finally, an external command, including its own options and flags may be passed. If so, this should be separated by a double dash.



For example,



my_command test



should result in



"$inp" == "test"
"$flag" == ""
"$ext_command" == ""


and



my_command this is a test -new -- sed "s|a|b|"



should result in



"$inp" == "this is a test"
"$flag" == "new"
"$ext_command" == "sed "s|a|b""


I think the following script does what I want, but since it's my first bash script, I wanted to ask whether the script is idiomatic and whether I missed any border cases.



local inp=""
local flag=""
local ext_command=""
local count="1"
local started=""
for i
do
count=$((count+1))
if [[ "$i" == '--' ]]
then
ext_command="${@:count}"
break
else
if [[ "$i" == -* ]];
then
flag=${i#*-}
else
if [ ! "$started" ]
then
inp="$i"
started=1
else
inp="$inp $i"
fi
fi
fi
done









share|improve this question









New contributor




Bananach is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.




















  • Is it possible to have multiple flags?
    – Solomon Ucko
    2 days ago






  • 1




    @SolomonUcko no, not at the moment
    – Bananach
    2 days ago















up vote
2
down vote

favorite












In a bash script, I need to parse arguments that have the following form:




  1. The main arguments can be thought of as being a single argument, but I do not want to force users to quote the entire thing, so when the argument contains spaces I must be able to handle multiple arguments.


  2. One flag may be passed as -<flag> where <flag> can be an arbitrary word (without spaces)


  3. Finally, an external command, including its own options and flags may be passed. If so, this should be separated by a double dash.



For example,



my_command test



should result in



"$inp" == "test"
"$flag" == ""
"$ext_command" == ""


and



my_command this is a test -new -- sed "s|a|b|"



should result in



"$inp" == "this is a test"
"$flag" == "new"
"$ext_command" == "sed "s|a|b""


I think the following script does what I want, but since it's my first bash script, I wanted to ask whether the script is idiomatic and whether I missed any border cases.



local inp=""
local flag=""
local ext_command=""
local count="1"
local started=""
for i
do
count=$((count+1))
if [[ "$i" == '--' ]]
then
ext_command="${@:count}"
break
else
if [[ "$i" == -* ]];
then
flag=${i#*-}
else
if [ ! "$started" ]
then
inp="$i"
started=1
else
inp="$inp $i"
fi
fi
fi
done









share|improve this question









New contributor




Bananach is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.




















  • Is it possible to have multiple flags?
    – Solomon Ucko
    2 days ago






  • 1




    @SolomonUcko no, not at the moment
    – Bananach
    2 days ago













up vote
2
down vote

favorite









up vote
2
down vote

favorite











In a bash script, I need to parse arguments that have the following form:




  1. The main arguments can be thought of as being a single argument, but I do not want to force users to quote the entire thing, so when the argument contains spaces I must be able to handle multiple arguments.


  2. One flag may be passed as -<flag> where <flag> can be an arbitrary word (without spaces)


  3. Finally, an external command, including its own options and flags may be passed. If so, this should be separated by a double dash.



For example,



my_command test



should result in



"$inp" == "test"
"$flag" == ""
"$ext_command" == ""


and



my_command this is a test -new -- sed "s|a|b|"



should result in



"$inp" == "this is a test"
"$flag" == "new"
"$ext_command" == "sed "s|a|b""


I think the following script does what I want, but since it's my first bash script, I wanted to ask whether the script is idiomatic and whether I missed any border cases.



local inp=""
local flag=""
local ext_command=""
local count="1"
local started=""
for i
do
count=$((count+1))
if [[ "$i" == '--' ]]
then
ext_command="${@:count}"
break
else
if [[ "$i" == -* ]];
then
flag=${i#*-}
else
if [ ! "$started" ]
then
inp="$i"
started=1
else
inp="$inp $i"
fi
fi
fi
done









share|improve this question









New contributor




Bananach is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.











In a bash script, I need to parse arguments that have the following form:




  1. The main arguments can be thought of as being a single argument, but I do not want to force users to quote the entire thing, so when the argument contains spaces I must be able to handle multiple arguments.


  2. One flag may be passed as -<flag> where <flag> can be an arbitrary word (without spaces)


  3. Finally, an external command, including its own options and flags may be passed. If so, this should be separated by a double dash.



For example,



my_command test



should result in



"$inp" == "test"
"$flag" == ""
"$ext_command" == ""


and



my_command this is a test -new -- sed "s|a|b|"



should result in



"$inp" == "this is a test"
"$flag" == "new"
"$ext_command" == "sed "s|a|b""


I think the following script does what I want, but since it's my first bash script, I wanted to ask whether the script is idiomatic and whether I missed any border cases.



local inp=""
local flag=""
local ext_command=""
local count="1"
local started=""
for i
do
count=$((count+1))
if [[ "$i" == '--' ]]
then
ext_command="${@:count}"
break
else
if [[ "$i" == -* ]];
then
flag=${i#*-}
else
if [ ! "$started" ]
then
inp="$i"
started=1
else
inp="$inp $i"
fi
fi
fi
done






bash shell






share|improve this question









New contributor




Bananach is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.











share|improve this question









New contributor




Bananach is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.









share|improve this question




share|improve this question








edited 2 days ago









Toby Speight

22.3k537109




22.3k537109






New contributor




Bananach is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.









asked 2 days ago









Bananach

1113




1113




New contributor




Bananach is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.





New contributor





Bananach is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.






Bananach is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.












  • Is it possible to have multiple flags?
    – Solomon Ucko
    2 days ago






  • 1




    @SolomonUcko no, not at the moment
    – Bananach
    2 days ago


















  • Is it possible to have multiple flags?
    – Solomon Ucko
    2 days ago






  • 1




    @SolomonUcko no, not at the moment
    – Bananach
    2 days ago
















Is it possible to have multiple flags?
– Solomon Ucko
2 days ago




Is it possible to have multiple flags?
– Solomon Ucko
2 days ago




1




1




@SolomonUcko no, not at the moment
– Bananach
2 days ago




@SolomonUcko no, not at the moment
– Bananach
2 days ago










1 Answer
1






active

oldest

votes

















up vote
2
down vote













Don't go against the language




The main arguments can be thought of as being a single argument, but I do not want to force users to quote the entire thing, so when the argument contains spaces I must be able to handle multiple arguments.




If you want to a value containing spaces as a single argument,
then you and your users should double-quote it,
otherwise Bash will perform word splitting.
This is a fundamental principle in Bash,
and it's better to play along with it than to go against.



Trying to go against will get you into all kinds of trouble.
For example, what would you expect for?



my_command this      is   a test -new -- sed "s|a|b|"


That is, multiple spaces between words.
Those spaces will be lost,
the script will behave the same way as if there was a single space in between.



Keep in mind that users will have to quote special characters anyway.
You cannot shelter them from quoting.
It's better to learn the basic rules of word splitting and quoting early,
rather than trying to work around it with hacky solutions.



Assign arrays to arrays



This statement assigns an array to a non-array:




ext_command="${@:count}"



This way you lose the ability to expand the original value correctly quoted.



Take for example this input:




my_command test -new -- sed "s|a| |"



Notice the space in the sed pattern.



And let's say the script uses ext_command like this:



ls | "$ext_command"


This will not work as intended (replacing "a" with spaces), because the original arguments are not preserved correctly.



Using an array you could leave this option open, that is:



ext_command=("${@:count}")


And then later:



ls | "${ext_command[@]}"


Minor points



Instead of this:




local inp=""



You can write simply:



local inp




Instead of this:




count=$((count+1))



You can write simply:



((count++))




Instead of this:




if [[ "$i" == '--' ]]
then
ext_command="${@:count}"
break
else
# a long block of code ...
fi



It's more readable like this:



if [[ "$i" == '--' ]]
then
ext_command="${@:count}"
break
fi

# a long block of code ... but less deeply nested





share|improve this answer





















    Your Answer





    StackExchange.ifUsing("editor", function () {
    return StackExchange.using("mathjaxEditing", function () {
    StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
    StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
    });
    });
    }, "mathjax-editing");

    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "196"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    convertImagesToLinks: false,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: null,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });






    Bananach is a new contributor. Be nice, and check out our Code of Conduct.










     

    draft saved


    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f208072%2fsimple-bash-argument-parser%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    2
    down vote













    Don't go against the language




    The main arguments can be thought of as being a single argument, but I do not want to force users to quote the entire thing, so when the argument contains spaces I must be able to handle multiple arguments.




    If you want to a value containing spaces as a single argument,
    then you and your users should double-quote it,
    otherwise Bash will perform word splitting.
    This is a fundamental principle in Bash,
    and it's better to play along with it than to go against.



    Trying to go against will get you into all kinds of trouble.
    For example, what would you expect for?



    my_command this      is   a test -new -- sed "s|a|b|"


    That is, multiple spaces between words.
    Those spaces will be lost,
    the script will behave the same way as if there was a single space in between.



    Keep in mind that users will have to quote special characters anyway.
    You cannot shelter them from quoting.
    It's better to learn the basic rules of word splitting and quoting early,
    rather than trying to work around it with hacky solutions.



    Assign arrays to arrays



    This statement assigns an array to a non-array:




    ext_command="${@:count}"



    This way you lose the ability to expand the original value correctly quoted.



    Take for example this input:




    my_command test -new -- sed "s|a| |"



    Notice the space in the sed pattern.



    And let's say the script uses ext_command like this:



    ls | "$ext_command"


    This will not work as intended (replacing "a" with spaces), because the original arguments are not preserved correctly.



    Using an array you could leave this option open, that is:



    ext_command=("${@:count}")


    And then later:



    ls | "${ext_command[@]}"


    Minor points



    Instead of this:




    local inp=""



    You can write simply:



    local inp




    Instead of this:




    count=$((count+1))



    You can write simply:



    ((count++))




    Instead of this:




    if [[ "$i" == '--' ]]
    then
    ext_command="${@:count}"
    break
    else
    # a long block of code ...
    fi



    It's more readable like this:



    if [[ "$i" == '--' ]]
    then
    ext_command="${@:count}"
    break
    fi

    # a long block of code ... but less deeply nested





    share|improve this answer

























      up vote
      2
      down vote













      Don't go against the language




      The main arguments can be thought of as being a single argument, but I do not want to force users to quote the entire thing, so when the argument contains spaces I must be able to handle multiple arguments.




      If you want to a value containing spaces as a single argument,
      then you and your users should double-quote it,
      otherwise Bash will perform word splitting.
      This is a fundamental principle in Bash,
      and it's better to play along with it than to go against.



      Trying to go against will get you into all kinds of trouble.
      For example, what would you expect for?



      my_command this      is   a test -new -- sed "s|a|b|"


      That is, multiple spaces between words.
      Those spaces will be lost,
      the script will behave the same way as if there was a single space in between.



      Keep in mind that users will have to quote special characters anyway.
      You cannot shelter them from quoting.
      It's better to learn the basic rules of word splitting and quoting early,
      rather than trying to work around it with hacky solutions.



      Assign arrays to arrays



      This statement assigns an array to a non-array:




      ext_command="${@:count}"



      This way you lose the ability to expand the original value correctly quoted.



      Take for example this input:




      my_command test -new -- sed "s|a| |"



      Notice the space in the sed pattern.



      And let's say the script uses ext_command like this:



      ls | "$ext_command"


      This will not work as intended (replacing "a" with spaces), because the original arguments are not preserved correctly.



      Using an array you could leave this option open, that is:



      ext_command=("${@:count}")


      And then later:



      ls | "${ext_command[@]}"


      Minor points



      Instead of this:




      local inp=""



      You can write simply:



      local inp




      Instead of this:




      count=$((count+1))



      You can write simply:



      ((count++))




      Instead of this:




      if [[ "$i" == '--' ]]
      then
      ext_command="${@:count}"
      break
      else
      # a long block of code ...
      fi



      It's more readable like this:



      if [[ "$i" == '--' ]]
      then
      ext_command="${@:count}"
      break
      fi

      # a long block of code ... but less deeply nested





      share|improve this answer























        up vote
        2
        down vote










        up vote
        2
        down vote









        Don't go against the language




        The main arguments can be thought of as being a single argument, but I do not want to force users to quote the entire thing, so when the argument contains spaces I must be able to handle multiple arguments.




        If you want to a value containing spaces as a single argument,
        then you and your users should double-quote it,
        otherwise Bash will perform word splitting.
        This is a fundamental principle in Bash,
        and it's better to play along with it than to go against.



        Trying to go against will get you into all kinds of trouble.
        For example, what would you expect for?



        my_command this      is   a test -new -- sed "s|a|b|"


        That is, multiple spaces between words.
        Those spaces will be lost,
        the script will behave the same way as if there was a single space in between.



        Keep in mind that users will have to quote special characters anyway.
        You cannot shelter them from quoting.
        It's better to learn the basic rules of word splitting and quoting early,
        rather than trying to work around it with hacky solutions.



        Assign arrays to arrays



        This statement assigns an array to a non-array:




        ext_command="${@:count}"



        This way you lose the ability to expand the original value correctly quoted.



        Take for example this input:




        my_command test -new -- sed "s|a| |"



        Notice the space in the sed pattern.



        And let's say the script uses ext_command like this:



        ls | "$ext_command"


        This will not work as intended (replacing "a" with spaces), because the original arguments are not preserved correctly.



        Using an array you could leave this option open, that is:



        ext_command=("${@:count}")


        And then later:



        ls | "${ext_command[@]}"


        Minor points



        Instead of this:




        local inp=""



        You can write simply:



        local inp




        Instead of this:




        count=$((count+1))



        You can write simply:



        ((count++))




        Instead of this:




        if [[ "$i" == '--' ]]
        then
        ext_command="${@:count}"
        break
        else
        # a long block of code ...
        fi



        It's more readable like this:



        if [[ "$i" == '--' ]]
        then
        ext_command="${@:count}"
        break
        fi

        # a long block of code ... but less deeply nested





        share|improve this answer












        Don't go against the language




        The main arguments can be thought of as being a single argument, but I do not want to force users to quote the entire thing, so when the argument contains spaces I must be able to handle multiple arguments.




        If you want to a value containing spaces as a single argument,
        then you and your users should double-quote it,
        otherwise Bash will perform word splitting.
        This is a fundamental principle in Bash,
        and it's better to play along with it than to go against.



        Trying to go against will get you into all kinds of trouble.
        For example, what would you expect for?



        my_command this      is   a test -new -- sed "s|a|b|"


        That is, multiple spaces between words.
        Those spaces will be lost,
        the script will behave the same way as if there was a single space in between.



        Keep in mind that users will have to quote special characters anyway.
        You cannot shelter them from quoting.
        It's better to learn the basic rules of word splitting and quoting early,
        rather than trying to work around it with hacky solutions.



        Assign arrays to arrays



        This statement assigns an array to a non-array:




        ext_command="${@:count}"



        This way you lose the ability to expand the original value correctly quoted.



        Take for example this input:




        my_command test -new -- sed "s|a| |"



        Notice the space in the sed pattern.



        And let's say the script uses ext_command like this:



        ls | "$ext_command"


        This will not work as intended (replacing "a" with spaces), because the original arguments are not preserved correctly.



        Using an array you could leave this option open, that is:



        ext_command=("${@:count}")


        And then later:



        ls | "${ext_command[@]}"


        Minor points



        Instead of this:




        local inp=""



        You can write simply:



        local inp




        Instead of this:




        count=$((count+1))



        You can write simply:



        ((count++))




        Instead of this:




        if [[ "$i" == '--' ]]
        then
        ext_command="${@:count}"
        break
        else
        # a long block of code ...
        fi



        It's more readable like this:



        if [[ "$i" == '--' ]]
        then
        ext_command="${@:count}"
        break
        fi

        # a long block of code ... but less deeply nested






        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered yesterday









        janos

        96.6k12122349




        96.6k12122349






















            Bananach is a new contributor. Be nice, and check out our Code of Conduct.










             

            draft saved


            draft discarded


















            Bananach is a new contributor. Be nice, and check out our Code of Conduct.













            Bananach is a new contributor. Be nice, and check out our Code of Conduct.












            Bananach is a new contributor. Be nice, and check out our Code of Conduct.















             


            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f208072%2fsimple-bash-argument-parser%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Quarter-circle Tiles

            build a pushdown automaton that recognizes the reverse language of a given pushdown automaton?

            Mont Emei