Sunday, October 20, 2013

Shell Scripting - Best Practices

Most programming languages have a set of "best practices" that should be followed when writing code in that language. However, I have not been able to find a comprehensive one for shell scripting so have decided to write my own based on my experience writing shell scripts over the years.

A note on portability: Since I mainly write shell scripts to run on systems which have Bash 4.2 installed, I don't need to worry about portability much, but you might need to! The list below is written with Bash 4.2 (and other modern shells) in mind. If you are writing a portable script, some points will not apply. Needless to say, you should perform sufficient testing after making any changes based on this list :-)

Here is my list of best practices for shell scripting (in no particular order):

  1. Use functions
  2. Document your functions
  3. Use shift to read function arguments
  4. Declare your variables
  5. Quote all parameter expansions
  6. Use arrays where appropriate
  7. Use "$@" to refer to all arguments
  8. Use uppercase variable names for environment variables only
  9. Prefer shell builtins over external programs
  10. Avoid unnecessary pipelines
  11. Avoid parsing ls
  12. Use globbing
  13. Use null delimited output where possible
  14. Don't use backticks
  15. Use process substitution instead of creating temporary files
  16. Use mktemp if you have to create temporary files
  17. Use [[ and (( for test conditions
  18. Use commands in test conditions instead of exit status
  19. Use set -e
  20. Write error messages to stderr

Each one of the points above is described in some detail below.

  1. Use functions

    Unless you're writing a very small script, use functions to modularise your code and make it more readable, reusable and maintainable. The template I use for all my scripts is shown below. As you can see, all code is written inside functions. The script starts off with a call to the main function.

    #!/bin/bash
    set -e
    
    usage() {
    }
    
    my_function() {
    }
    
    main() {
    }
    
    main "$@"
    
  2. Document your functions

    Add sufficient documentation to your functions to specify what they do and what arguments are required to invoke them. Here is an example:

    # Processes a file.
    # $1 - the name of the input file
    # $2 - the name of the output file
    process_file(){
    }
    
  3. Use shift to read function arguments

    Instead of using $1, $2 etc to pick up function arguments, use shift as shown below. This makes it easier to reorder arguments, if you change your mind later.

    # Processes a file.
    # $1 - the name of the input file
    # $2 - the name of the output file
    process_file(){
        local -r input_file="$1";  shift
        local -r output_file="$1"; shift
    }
    
  4. Declare your variables

    If your variable is an integer, declare it as such. Also, make all your variables readonly unless you intend to change their value later in your script. Use local for variables declared within functions. This helps convey your intent. If portability is a concern, use typeset instead of declare. Here are a few examples:

    declare -r -i port_number=8080
    declare -r -a my_array=( apple orange )
    
    my_function() {
        local -r name=apple
    }
    
  5. Quote all parameter expansions

    To prevent word-splitting and file globbing you must quote all variable expansions. In particular, you must do this if you are dealing with filenames that may contain whitespace (or other special characters). Consider this example:

    # create a file containing a space in its name
    touch "foo bar"
    
    declare -r my_file="foo bar"
    
    # try rm-ing the file without quoting the variable
    rm  $my_file
    # it fails because rm sees two arguments: "foo" and "bar"
    # rm: cannot remove `foo': No such file or directory
    # rm: cannot remove `bar': No such file or directory
    
    # need to quote the variable
    rm "$my_file"
    
    # file globbing example:
    mesg="my pattern is *.txt"
    echo $mesg
    # this is not quoted so *.txt will undergo expansion
    # will print "my pattern is foo.txt bar.txt"
    
    # need to quote it for correct output
    echo "$msg"
    
    

    It's good practice to quote all your variables. If you do need word-splitting, consider using an array instead. See the next point.

  6. Use arrays where appropriate

    Don't store a collection of elements in a string. Use an array instead. For example:

    # using a string to hold a collection
    declare -r hosts="host1 host2 host3"
    for host in $hosts  # not quoting $hosts here, since we want word splitting
    do
        echo "$host"
    done
    
    # use an array instead!
    declare -r -a host_array=( host1 host2 host3 )
    for host in "${host_array[@]}"
    do
        echo "$host"
    done
    
  7. Use "$@" to refer to all arguments

    Don't use $*. Refer to my previous post: Difference between $*, $@, "$*" and "$@". Here is an example:

    main() {
        # print each argument
        for i in "$@"
        do
            echo "$i"
        done
    }
    # pass all arguments to main
    main "$@"
    
  8. Use uppercase variable names for ENVIRONMENT variables only

    My personal preference is that all variables should be lowercase, except for environment variables. For example:

    declare -i port_number=8080
    
    # JAVA_HOME and CLASSPATH are environment variables
    "$JAVA_HOME"/bin/java -cp "$CLASSPATH" app.Main "$port_number"
    
  9. Prefer shell builtins over external programs

    The shell has the ability to manipulate strings and perform simple arithmetic so you don't need to invoke programs like cut and sed. Here are a few examples:

    declare -r my_file="/var/tmp/blah"
    
    # instead of dirname, use:
    declare -r file_dir="{my_file%/*}"
    
    # instead of basename, use:
    declare -r file_base="{my_file##*/}"
    
    # instead of sed 's/blah/hello', use:
    declare -r new_file="${my_file/blah/hello}"
    
    # instead of bc <<< "2+2", use:
    echo $(( 2+2 ))
    
    # instead of grepping a pattern in a string, use:
    [[ $line =~ .*blah$ ]]
    
    # instead of cut -d:, use an array:
    IFS=: read -a arr <<< "one:two:three"
    

    Note that an external program will perform better when operating on large files/input.

  10. Avoid unnecessary pipelines

    Pipelines add extra overhead to your script so try to keep your pipelines small. Common examples of useless pipelines are cat and echo, shown below:

    1. Avoid unnecessary cat

      If you are not familiar with the infamous Useless Use of Cat award, take a look here. The cat command should only be used for concatenating files, not for sending the output of a file to another command.

      # instead of
      cat file | command
      # use
      command < file
      
    2. Avoid unnecessary echo

      You should only use echo if you want to output some text to stdout, stderr, file etc. If you want to send text to another command, don't echo it through a pipe! Use a here-string instead. Note that here-strings are not portable (but most modern shells support them) so use a heredoc if you are writing a portable script. (See my earlier post: Useless Use of Echo.)

      # instead of
      echo text | command
      # use
      command <<< text
      
      # for portability, use a heredoc
      command << END
      text
      END
      
    3. Avoid unnecessary grep

      Piping from grep to awk or sed is unnecessary. Since both awk and sed can grep, you don't need the grep in your pipeline. (Check out my previous post: Useless Use of Grep.)

      # instead of
      grep pattern file | awk '{print $1}'
      # use
      awk '/pattern/{print $1}' file
      
      # instead of
      grep pattern file | sed 's/foo/bar/g'
      # use
      sed -n '/pattern/{s/foo/bar/p}' file
      
    4. Other unnecessary pipelines

      Here are a few other examples:

      # instead of
      command | sort | uniq
      # use
      command | sort -u
      
      # instead of
      command | grep pattern | wc -l
      # use
      command | grep -c pattern
      
  11. Avoid parsing ls

    The problem is that ls outputs filenames separated by newlines, so if you have a filename containing a newline character you won't be able to parse it correctly. It would be nice if ls could output null delimited filenames but, unfortunately, it can't. Instead of ls, use file globbing or an alternative command which outputs null terminated filenames, such as find -print0.

  12. Use globbing

    Globbing (or filename expansion) is the shell's way of generating a list of files matching a pattern. In bash, you can make globbing more powerful by enabling extended pattern matching operators using the extglob shell option. Also, enable nullglob so that you get an empty list if no matches are found. Globbing can be used instead of find in some cases and, once again, don't parse ls! Here are a couple of examples:

    
    shopt -s nullglob
    shopt -s extglob
    
    # get all files with a .yyyymmdd.txt suffix
    declare -a dated_files=( *.[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9].txt )
    
    # get all non-zip files
    declare -a non_zip_files=( !(*.zip) )
    
    
  13. Use null delimited output where possible

    In order to correctly handle filenames containing whitespace and newline characters, you should use null delimited output, which results in each line being terminated by a NUL (\000) character instead of a newline. Most programs support this. For example, find -print0 outputs filenames followed by a null character and xargs -0 reads arguments separated by null characters.

    # instead of
    find . -type f -mtime +5 | xargs rm -f
    # use
    find . -type f -mtime +5 -print0 | xargs -0 rm -f
    
    # looping over files
    find . -type f -print0 | while IFS= read -r -d $'\0' filename; do
        echo "$filename"
    done
    
  14. Don't use backticks

    Use $(command) instead of `command` because it is easier to nest multiple commands and makes your code more readable. Here is a simple example:

    # ugly escaping required when using nested backticks
    a=`command1 \`command2\``
    
    # $(...) is cleaner
    b=$(command1 $(command2))
    
  15. Use process substitution instead of creating temporary files

    In most cases, if a command takes a file as an input, the file can be replaced by the output of another command using process substitution: <(command). This saves you from having to write out a temp file, passing that temp file to the command and finally deleting the temp file. This is shown below:

    # using temp files
    command1 > file1
    command2 > file2
    diff file1 file2
    rm file1 file2
    
    # using process substitution
    diff <(command1) <(command2)
    
  16. Use mktemp if you have to create temporary files

    Try to avoid creating temporary files. If you must, use mktemp to create a temporary directory and then write your files to it. Make sure you remove the directory after you are done.

    # set up a trap to delete the temp dir when the script exits
    unset temp_dir
    trap '[[ -d "$temp_dir" ]] && rm -rf "$temp_dir"' EXIT
    
    # create the temp dir
    declare -r temp_dir=$(mktemp -dt myapp.XXXXXX)
    
    # write to the temp dir
    command > "$temp_dir"/foo
    
  17. Use [[ and (( for test conditions

    Prefer [[ ... ]] over [ ... ] because it is safer and provides a richer set of features. Use (( ... )) for arithmetic conditions because it allows you to perform comparisons using familiar mathematical operators such as < and > instead of -lt and -gt. Note that if you desire portability, you have to stick to the old-fashioned [ ... ]. Here are a few examples:

    [[ $foo == "foo" ]] && echo "match"  # don't need to quote variable inside [[
    [[ $foo == "a" && $bar == "a" ]] && echo "match"
    
    declare -i num=5
    (( num < 10 )) && echo "match"       # don't need the $ on $num in ((
    
  18. Use commands in test conditions instead of exit status

    If you want to check whether a command succeeded before doing something, use the command directly in the condition of your if-statement instead of checking the command's exit status.

    
    # don't use exit status
    grep -q pattern file
    if (( $? == 0 ))
    then
        echo "pattern was found"
    fi
    
    # use the command as the condition
    if grep -q pattern file
    then
        echo "pattern was found"
    fi
    
  19. Use set -e

    Put this at the top of your script. This tells the shell to exit the script as soon as any statement returns a non-zero exit code.

  20. Write error messages to stderr

    Error messages belong on stderr not stdout.

    echo "An error message" >&2
    

If you have any other suggestions for my list, please share them in the comments section below!

55 comments:

  1. Your link to Difference between $*, $@, "$*" and "$@" points to the wrong place ;-)
    Next stop: a Sonar quality profile for shell scripts?

    ReplyDelete
  2. Well spotted, Gavin! I have fixed the link now. Thanks for reading :-)

    ReplyDelete
  3. Anonymous3:55 PM

    It's easier to port a shell, than a shell script, my shell-veteran friend likes to say. There's more to that then just a grain of truth. Other than that, I found myself quite in agreement!

    ReplyDelete
  4. You gathered a lot of useful recommendations that could be useful to *many* shell programmers! Maybe you could identify “bashisms” in your text, I noted 13 and 17. I am currently having a lot of trouble with bugs in Bash pledging its job management – Bash regularly core dumps!

    I just wrote a short text about a common error of shell beginners, consisting in implementing complex treatments in the shell, while these should be delegated to filters. I would love to read your comments on this text! http://unix-workstation.blogspot.de/2015/04/delegating-complex-treatments-to.html

    ReplyDelete
  5. Thietkenhadepmoi.vn - công ty thiết kế nhà đẹp uy tín. Thiết kế nhà - thiết kế nhà đẹp mới - thiết kế thi công nhà đẹp - thiết kế xây dựng nhà đẹp - mẫu nhà đẹp mới. Tổng hợp các mẫu nhà mái thái đẹp - mẫu nhà phố đẹp - mẫu biệt thự đẹp sang trọng.
    Liên hệ ngay với công ty Thiết kế nhà đẹp mới để được tư vấn và báo giá miễn phí!

    Từ khóa thiết kế nhà đẹp mới :
    #thietkenhadepmoi #thietkenhadepmoivn #thietkennha #thietkennhadep #maunhadepmoi #thietkennhadepmoi #thietkennhauytin #maubietthudep #maunhamaithaidep #mauthietkenhadep #thietkexaydungnhadep #maunhaphodep #congtythietkenhadep #thietkethicongnhadep #thietkexaydungnhadep #maunhadepmoi #maunhamoi #xaydungnhadep #congtynhadep

    https://visual.ly/users/thietkenhadepmoivn/portfolio
    https://www.zoimas.com/profile/thietkenhadepmoivn
    https://www.woddal.com/thietkenhadepmoivn
    https://pawoo.net/@thietkenhadepmoivn
    https://player.me/thiekenhadepmoivn/about

    Website: Thietkenhadepmoi.vn

    ReplyDelete
  6. Struggling with Quickbooks Enterprise Support Phone Number ? Fix it by dialling us at 1-855-511-6911. The error usually arrives due to improper installation of QuickBooks upgrading.

    ReplyDelete
  7. If confronting any sort of technical glitch in the software. Resolve it by calling on QuickBooks POS Support Number|+1(844)233-3033. Our learned & Experienced QuickBooks professionals are always presented to offer technical help. For More Visit: https://247quikbooks-support.com/quickbooks-pos-support-phone-number-usa/

    ReplyDelete
  8. If confronting any sort of technical glitch in the software. Resolve it by calling on +1(844)233-3033 QuickBooks Payroll Support Phone Number. Our learned & Experienced QuickBooks professionals are always presented to offer technical help. For More Visit: https://247quikbooks-support.com/quickbooks-payroll-support-phone-number/

    ReplyDelete
  9. It’s very informative and you are obviously very knowledgeable in this area. You have opened my eyes to varying views on this topic with interesting and solid content and call us QuickBooks Enterprise Support Phone Number +1(844)233-3033. For more deatils and information visit our website: https://247quikbooks-support.com/quickbooks-enterprise-support-phone-number-usa/

    ReplyDelete
  10. Very interesting blog. Many blogs I see these days do not really provide anything that attracts others, but believe me the way you interact is literally awesome.You can also check my articles as well.

    python training in bangalore

    python training in hyderabad

    python online training

    python training

    python flask training

    python flask online training

    python training in coimbatore

    ReplyDelete
  11. An overwhelming web journal I visit this blog, it's unfathomably amazing. Unusually, in this present blog's substance made inspiration driving truth and reasonable. The substance of data is enlightening.


    Full Stack Course Chennai
    Full Stack Training in Bangalore

    Full Stack Course in Bangalore

    Full Stack Training in Hyderabad

    Full Stack Course in Hyderabad

    Full Stack Training

    Full Stack Course

    Full Stack Online Training

    Full Stack Online Course




    ReplyDelete
  12. Am really impressed about this blog because this blog is very easy to learn and understand clearly.This blog is very useful for the college students and researchers to take a good notes in good manner,I gained many unknown information.
    Data Science Training In Chennai

    Data Science Online Training In Chennai

    Data Science Training In Bangalore

    Data Science Training In Hyderabad

    Data Science Training In Coimbatore

    Data Science Training

    Data Science Online Training

    ReplyDelete
  13. Thanks for provide great informatics and looking beautiful blog, really nice required information & the things i never imagined and i would request, wright more blog and blog post like that for us. Thanks you
    DevOps Training in Chennai

    DevOps Online Training in Chennai

    DevOps Training in Bangalore

    DevOps Training in Hyderabad

    DevOps Training in Coimbatore

    DevOps Training

    DevOps Online Training

    ReplyDelete
  14. Being a QuickBooks POS user if you need help with glitches that you come across quite often then make a call at our QuickBooks POS Support Phone Number +1(844)233-3033 This number empowers you to get optimal support from experts

    ReplyDelete
  15. Very interesting blog. Many blogs I see these days do not really provide anything that attracts others, but believe me the way you interact is literally awesome.You can also check my articles as well.

    Security Guard License
    Ontario Security License
    Security License Ontario
    Security License

    Thank you..

    ReplyDelete
  16. Get Your Error and Problem Solve With QuickBooks Expert 24*7 live.
    Click Here to Know How to Fix QuickBooks error 403 to get in touch for
    More Details Dial : 1-844-514-7111

    ReplyDelete
  17. Well explained and informative blog. To get QuickBooks Support click bellow

    QuickBooks Customer Service Phone Number 1-855-662-2O4O
    QuickBooks Phone Number 1-855-662-2O4O
    QuickBooks Support Number 1-855-662-2O4O
    QuickBooks Support Phone Number 1-855-662-2O4O
    QuickBooks Customer Service Number 1-855-662-2O4O
    QuickBooks Customer Service 1-855-662-2O4O
    QuickBooks Customer Support Number 1-855-662-2O4O
    QuickBooks Customer Support 1-855-662-2O4O

    ReplyDelete
  18. Nice Blog !
    Are you getting frustrated while experiencing technical glitches & issues in QuickBooks For Mac? Don’t get troubled!! Here is the solution!! By just dialing our QuickBooks For Mac Support Phone Number 1-888-597-O22O.

    ReplyDelete
  19. Chuyennhasgthanhhung.com - là công ty cung cấp dịch vụ chuyển nhà uy tín ✅✅✅ Liên hệ ngay chuyển nhà trọn gói Hà Nội - chuyển nhà tại Hà Nội để được tư vấn và khảo sát miễn phía
    Từ khóa chuyển nhà trọn gói Thành Hưng:
    #chuyennhatrongoihanoi #chuyennhahanoi #chuyennhataihanoi #dichvuchuyennhahanoi #dichvuchuyennhatrongoihanoi #vanchuyennhahanoi #chuyennhatrongoithanhhung #chuyennhathanhhung
    Hệ thống social chuyển nhà Thành Hưng:
    http://chuyennnhahanoi.puzl.com/
    https://chuyennhasgthanhhung.edublogs.org/
    https://chuyennhasgthanhhung.hpage.com/
    hhttps://chuyennhasgthanhhung.weebly.com/
    hhttps://gumroad.com/chuyennhasgthanhhung

    Website: Chuyennhasgthanhhung.com

    ReplyDelete
  20. Chuyennhasgthanhhung.com - là công ty cung cấp dịch vụ chuyển nhà uy tín ✅✅✅ Liên hệ ngay chuyển nhà trọn gói Hà Nội - chuyển nhà tại Hà Nội để được tư vấn và khảo sát miễn phía
    Từ khóa chuyển nhà trọn gói Thành Hưng:
    #chuyennhatrongoihanoi #chuyennhahanoi #chuyennhataihanoi #dichvuchuyennhahanoi #dichvuchuyennhatrongoihanoi #vanchuyennhahanoi #chuyennhatrongoithanhhung #chuyennhathanhhung
    Hệ thống social chuyển nhà Thành Hưng:
    http://chuyennnhahanoi.puzl.com/
    https://chuyennhasgthanhhung.edublogs.org/
    https://chuyennhasgthanhhung.hpage.com/
    hhttps://chuyennhasgthanhhung.weebly.com/
    hhttps://gumroad.com/chuyennhasgthanhhung

    Website: Chuyennhasgthanhhung.com

    ReplyDelete
  21. Keep it up and update more valuable information like this. If you need website designing and web development services in the best price in Delhi, visit Ogen Infosystem and get website in your budget as you need.
    Website Designing Company

    ReplyDelete
  22. QuickBooks Payroll Error 61102 occurs mainly when users send the payroll information to Intuit without giving PIN or password.If you still have any doubt or query regarding the process, you can consult our QuickBooks experts at 1-855-662-2O4O.

    ReplyDelete
  23. Chuyennhasgthanhhung.com - là công ty cung cấp dịch vụ chuyển nhà uy tín ✅✅✅ Liên hệ ngay chuyển nhà trọn gói Hà Nội - chuyển nhà tại Hà Nội để được tư vấn và khảo sát miễn phía
    Từ khóa chuyển nhà trọn gói Thành Hưng:
    #chuyennhatrongoihanoi #chuyennhahanoi #chuyennhataihanoi #dichvuchuyennhahanoi #dichvuchuyennhatrongoihanoi #vanchuyennhahanoi #chuyennhatrongoithanhhung #chuyennhathanhhung
    Hệ thống social chuyển nhà Thành Hưng:
    https://chuyennhasgthanhhung.bcz.com/
    http://vantaithanhhung.mystrikingly.com/
    http://chuyennnhahanoi.puzl.com/
    https://chuyennha.godaddysites.com/
    hhttps://chuyennhasgthanhhung.edublogs.org/

    Website: Chuyennhasgthanhhung.com


    ReplyDelete
  24. Are you experiencing miscellaneous technical defects in QuickBooks Desktop on a frequent basis? Don’t worry!! Just dial our QuickBooks Desktop Support Number +1(844)233-3033, and get all your queries settled at once. Our executives always attempt to ensure that you gain all the essential information, support,
    https://local.exactseek.com/detail/quickbooks-pro-support-phone-number18442333033-512056

    ReplyDelete
  25. QuickBooks Payroll Error 20102 primarily persists while signing up for Direct Deposit on the EIN linked on the previous account.If you encounter such issues, (Error 20102 ), make a call to our QuickBooks specialists at 1-855-662-2O4O and get benefited with optimal resolutions to your queries.

    ReplyDelete
  26. nice post!
    Worried About QuickBooks Error ?Get in touch with QuickBooks expert for instant solution.
    Click Here to know how to fix QuickBooks Error 15222
    Dial on QuickBooks toll-free Number for instant solution +1-888-603-9404

    ReplyDelete
  27. nice post!
    Worried About QuickBooks Error ?Get in touch with QuickBooks expert for instant solution.
    Dial QuickBooks Support Phone Number +1-888-603-9404

    ReplyDelete
  28. nice post!
    Worried About QuickBooks Error ?Get in touch with QuickBooks expert for instant solution.
    Click Here to know how to fix QuickBooks Error 1603
    Dial on QuickBooks Toll-free Number +1-888-603-9404

    ReplyDelete
  29. Struggling with Quickbooks Support Phone Number ? Fix it by dialling us at 1-855-533-6333. The error usually arrives due to improper installation of QuickBooks upgrading.

    ReplyDelete
  30. Nice Blog !
    Looking for comprehensive assistance while working on QuickBooks? No worries!! Just reach out to our executives by dialing our QuickBooks Phone Number 1-888-927-O94O.

    ReplyDelete
  31. Water bodies are the main source of transportation for international freight forwarding. Due to this, sea freight company in Delhi,
    visit
    Freight Forwarder in Vietnam
    Shipping Company In India

    ReplyDelete
  32. Usually, I never comment on blogs but your article is so convincing that I never stop myself to say something about it. You’re doing a great job Man, Keep it up. We provide the highest quality in mold remediation services, asbestos removal & water dry out in Maricopa County including Phoenix, Mesa, Chandler, Glendale, Scottsdale and Gilbert! for more information visit our website water damage restoration | Water Restoration experts | water cleanup experts phoenix

    ReplyDelete
  33. Such great information for the blogger I am a professional blogger thanks. The La Barbera proudly serves a great combination of modern and traditional barbery. for more information visit our website Barbershop | haircut near me

    ReplyDelete
  34. Putlocker - Putlockerc new site 2020 - Putlockers new site | Putlocker new | Putlockerc.to.

    Website: PUTLOCKER


    Hastag: #putlocker, putlockerc, #putlockers, /g/11c1wx49cy, /m/0735l, /m/02mzct, /g/12214wvs, /g/11fk1k97qr, /g/11ftxqgzvr, /g/11bwdpgj8t, /m/07s8gcr, /m/03ykjs9, /m/0wr2kl, /g/11hnyt48zn, /m/0873c4, /m/03x49v, /g/11h0b1qygf

    ReplyDelete
  35. Putlocker - Putlockerc new site 2020 - Putlockers new site | Putlocker new | Putlockerc.to.

    Website: Putlockers


    Hastag: #putlocker, putlockerc, #putlockers, /g/11c1wx49cy, /m/0735l, /m/02mzct, /g/12214wvs, /g/11fk1k97qr, /g/11ftxqgzvr, /g/11bwdpgj8t, /m/07s8gcr, /m/03ykjs9, /m/0wr2kl, /g/11hnyt48zn, /m/0873c4, /m/03x49v, /g/11h0b1qygf

    ReplyDelete
  36. Can you make youtube video how you practice? From here https://soclikes.com/ you can get youtube likes if you need it

    ReplyDelete
  37. Being a resident of Montana you don’t have to look for long to get expert assistance just dial QuickBooks Support Phone Number Montana +1(844)233-3033 As through this number, you will get in touch with experts who are ready to assist you any time of the day with
    https://tinyurl.com/y4kmunun
    https://tinyurl.com/y2pla8h5
    https://tinyurl.com/yxddfwpm
    https://tinyurl.com/y5f3ewsn
    https://tinyurl.com/y3h34b8a
    https://tinyurl.com/yx9t7236

    ReplyDelete
  38. Thanks for Sharing This Article.It is very so much valuable content. I hope these Commenting lists will help to my website
    devops online training
    best devops online training
    top devops online training

    ReplyDelete
  39. Did you know that you can easily view the contents of your phone on your TV without a cable? With a screen mirror app you can easily do the screen mirroring from Android to TV. Check out www.screenmirroring.me to find out more.

    ReplyDelete
  40. In such scenarios while getting stuck with any sort of technical or non-technical grievances in QuickBooks, simply call us on our QuickBooks Support Phone Number California +1(844)233-3033, and acquire exceptional accounting services from our executives. Our experts are skilled enough to answer all of the error codes users ask for.
    QuickBooks Desktop Support +1(844)233-3033
    QuickBooks Enterprise Support +1(844)233-3033
    Quickbooks 24/7 Customer service +1(844)233-3033
    Quickbooks Payroll Support +1(844)233-3033
    QuickBooks Technical Support +1(844)233-3033
    QuickBooks POS Support +1(844)233-3033
    Quickbooks payroll support phone number +1(844)233-3033
    QuickBooks customer service +1(844)233-3033

    ReplyDelete
  41. Shreeja Health Care is leading manufacturer of Oil Maker Machine. Shreeja Oil Extraction Machine is able to extract oil from various seeds like peanuts, Coconut, Sesame, Soybean, macadamia nuts, walnuts, sunflower seeds, vegetable seeds flaxseed etc.

    ReplyDelete
  42. Keytexmachines is leading CNC Machining Job Work in surat With Excellent Quality, Cost Effective Price & Prompt Delivery. CNC Machining Job Work, CNC Turning Job Work, CNC Machine Service offered by Keytex machines, Surat.

    ReplyDelete
  43. Yami immigration is a well-known and experienced immigration consultant in Surat. We provide Immigration Services for many countries such as Italy, Canada, France, Australia, Germany, USA, Malaysia, New Zealand, and Singapore.

    ReplyDelete