How to safely echo all arguments of a script? [duplicate] - bash

This question already has answers here:
Bash: echo string that starts with "-"
(4 answers)
Closed 2 years ago.
I am writing a bash script that must echo all of its arguments, which is surprisingly difficult to do.
The naive implementation looks like this:
#!/bin/bash
echo "$#"
However, that fails with input such as:
> ./script.sh -n -e -v -e -r
-v -e -r>
How can I make this more robust, such that the above results in:
> ./script.sh -n -e -v -e -r
-n -e -v -e -r
>

echo command's behavior may be different between systems. The safest way is to use printf:
printf '%s\n' "$*"
According to posix:
It is not possible to use echo portably across all POSIX systems unless both -n (as the first argument) and escape sequences are omitted.
The printf utility can be used portably to emulate any of the traditional behaviors of the echo utility ...

Using printf instead of echo:
#!/bin/bash
printf "%s " "$#"
printf "\n"

You can use printf as well :
#!/bin/bash
printf "%s\n" "$*"

This one adds a space in the beginning but is rather simple :
#!/bin/bash
echo "" "$#"

Related

Convert bash script into HP-UX os compatible

{ lineno1=`grep 'CustCare_CR*' /Abhi_data/Copy_test_demo/T2.txt`
echo $lineno1
var1=`sed -e 's#.*Backuped_CustCare/\(\)#\1#' <<< "$lineno1"`
echo $var1
path1="/CATALINA_HOME/Backuped_CustCare/$var1"
#echo $path1
cd $path1
pwd
}
When I run this code on Solaris it works, but when I run on HP-UX the <<< this operator does not work. Do you know any alternative to <<<?
Assuming that <<< is supposed to denote a here-string, one possibility would be to pipe the word into the sed command:
var1=`echo "$lineno1" | sed -e 's#.*Backuped_CustCare/\(\)#\1#'`
I would recommend the printf utility, instead, along with using $() instead of `:
var1=$(printf "%s" "$lineno1" | sed -e 's#.*Backuped_CustCare/\(\)#\1#')
References:
Why is printf better than echo?
What's the difference between $(stuff) and `stuff`?

BASH shell expand arguments with spaces from variable [duplicate]

This question already has answers here:
Bash doesn't parse quotes when converting a string to arguments
(5 answers)
Closed 6 years ago.
Say I have a variable $ARGS which contains the following:
file1.txt "second file.txt" file3.txt
How can I pass the contents of $ARGS as arguments to a command (say cat $ARGS, for example), treating "second file.txt" as one argument and not splitting it into "second and file.txt"?
Ideally, I'd like to be able to pass arguments to any command exactly as they are stored in a variable (read from a text file, but I don't think that's pertinent).
Thanks!
It's possible to do this without either bash arrays or eval: This is one of the few places where the behavior of xargs without either -0 or -d extensions (a behavior which mostly creates bugs) is actually useful.
# this will print each argument on a different line
# ...note that it breaks with arguments containing literal newlines!
xargs printf '%s\n' <<<"$ARGS"
...or...
# this will emit arguments in a NUL-delimited stream
xargs printf '%s\0' <<<"$ARGS"
# in bash 4.4, you can read this into an array like so:
readarray -t -d '' args < <(xargs printf '%s\0' <<<"$ARGS")
yourprog "${args[#]}" # actually run your programs
# in bash 3.x or newer, it's just a bit longer:
args=( );
while IFS= read -r -d '' arg; do
args+=( "$arg" )
done < <(xargs printf '%s\0' <<<"$ARGS")
yourprog "${args[#]}" # actually run your program
# in POSIX sh, you can't safely handle arguments with literal newlines
# ...but, barring that, can do it like this:
set --
while IFS= read -r arg; do
set -- "$#" "$arg"
done < <(printf '%s\n' "$ARGS" | xargs printf '%s\n')
yourprog "$#" # actually run your program
...or, letting xargs itself do the invocation:
# this will call yourprog with ARGS given
# ...but -- beware! -- will cause bugs if there are more arguments than will fit on one
# ...command line invocation.
printf '%s\n' "$ARGS" | xargs yourprog
As mentioned by Jonathan Leffler you can do this with an array.
my_array=( "file1.txt" "second file.txt" "file3.txt" )
cat "${my_array[1]}"
An array's index starts at 0. So if you wanted to cat the first file in your array you would use the index number 0. "${my_array[0]}". If you wanted to run your command on all elements, replace the index number with # or *. For instance instead of "${my_arryay[0]}" you would use "${my_array[#]}"Make sure you quote the array or it will treat any filename with spaces as separate files.
Alternatively if for some reason quoting the array is a problem, you can set IFS (which stands for Internal Field Separator) to equal a newline. If you do this, it's a good idea to save the default IFS to a variable before changing it so you can set it back to the way it was once the script completes. For instance:
# save IFS to a variable
old_IFS=${IFS-$' \t\n'}
#set IFS to a newline
IFS='$\n'
# run your script
my_array=( "file1.txt" "second file.txt" "file3.txt" )
cat ${my_array[1]}
# restore IFS to its default state
IFS=$old_IFS
It's probably better to not mess around with IFS unless you have to. If you can quote the array to make your script work then you should do that.
For a much more in depth look into using arrays see:
http://mywiki.wooledge.org/BashGuide/Arrays
http://wiki.bash-hackers.org/syntax/arrays
http://mywiki.wooledge.org/BashFAQ/005
Without bashisms, plain shell code might need an eval:
# make three temp files and list them.
cd /tmp ; echo ho > ho ; echo ho ho > "ho ho" ; echo ha > ha ;
A='ho "ho ho" ha' ; eval grep -n '.' $A
Output:
ho:1:ho
ho ho:1:ho ho
ha:1:ha
Note that eval is powerful, and if not used responsibly can lead to mischief...

Making bash script with command already containing '$1'

Somewhere I found this command that sorts lines in an input file by number of characters(1st order) and alphabetically (2nd order):
while read -r l; do echo "${#l} $l"; done < input.txt | sort -n | cut -d " " -f 2- > output.txt
It works fine but I would like to use the command in a bash script where the name of the file to be sorted is an argument:
& cat numbersort.sh
#!/bin/sh
while read -r l; do echo "${#l} $l"; done < $1 | sort -n | cut -d " " -f 2- > sorted-$1
Entering numbersort.sh input-txt doesn't give the desired result, probably because $1 is already in using as an argument for something else.
How do I make the command work in a shell script?
There's nothing wrong with your original script when used with simple arguments that don't involve quoting issues. That said, there are a few bugs addressed in the below version:
#!/bin/bash
while IFS= read -r line; do
printf '%d %s\n' "${#line}" "$line"
done <"$1" | sort -n | cut -d " " -f 2- >"sorted-$1"
Use #!/bin/bash if your goal is to write a bash script; #!/bin/sh is the shebang for POSIX sh scripts, not bash.
Clear IFS to avoid pruning leading and trailing whitespace from input and output lines
Use printf rather than echo to avoid ambiguities in the POSIX standard (see http://pubs.opengroup.org/onlinepubs/009604599/utilities/echo.html, particularly APPLICATION USAGE and RATIONALE sections).
Quote expansions ("$1" rather than $1) to prevent them from being word-split or glob-expanded
Note also that this creates a new file rather than operating in-place. If you want something that operates in-place, tack a && mv -- "sorted-$1" "$1" on the end.

Why is bash swallowing -e in the front of an array [duplicate]

This question already has answers here:
How do I echo "-e"?
(6 answers)
Closed 9 years ago.
Given the following syntax:
x=(-a 2);echo "${x[#]}";x=(-e 2 -e); echo "${x[#]}"
Output:
-a 2
2 -e
Desired output
-a 2
-e 2 -e
Why is this happening? How do I fix?
tl;dr
printf "%s\n" "${x[*]}"
Explanation
echo takes 3 options:
$ help echo
[…]
Options:
-n do not append a newline
-e enable interpretation of the following backslash escapes
-E explicitly suppress interpretation of backslash escapes
So if you run:
$ echo -n
$ echo -n -e
$ echo -n -e -E
You get nothing. Even if you put each option in quotes, it still looks the same to bash:
$ echo "-n"
$ echo "-n" "-e"
The last command runs echo with two arguments: -n and -e. Now contrast that with:
$ echo "-n -e"
-n -e
What we did was run echo with a single argument: -n -e. Since bash does not recognize the (combined) option -n -e, it finally echoes the single argument to the terminal like we want.
Applied to Arrays
In the second case, the array x begins with the element -e. After bash expands the array ${x[#]}, you are effectively running:
$ echo "-e" "2" "-e"
2 -e
Since the first argument is -e, it is interpreted as an option (instead of echoed to the terminal), as we already saw.
Now contrast that with the other style of array expansion ${x[*]}, which effectively does the following:
$ echo "-e 2 -e"
-e 2 -e
bash sees the single argument -e 2 -e — and since it does not recognize that as an option — it echoes the argument to the terminal.
Note that ${x[*]} style expansion is not safe in general. Take the following example:
$ x=(-e)
$ echo "${x[*]}"
Nothing is printed even though we expected -e to be echoed. If you've been paying attention, you already know why this is the case.
Escaping
The solution is to escape any arguments to the echo command. Unfortunately, unlike other commands which offer some way to say, “hey! the following argument is not to be interpreted as an option” (typically a -- argument), bash provides no such escaping mechanism for echo.
Fortunately there is the printf command, which provides a superset of the functionality that echo offers. Hence we arrive at the solution:
printf "%s\n" "${x[*]}"
#MichaelKropat's answer gives sufficient explanation.
As an alternative to echo (and printf), cat and a bash here-string can be used:
$ x=(-a 2);cat <<< "${x[#]}";x=(-e 2 -e); cat <<< "${x[#]}"
-a 2
-e 2 -e
$
Nice one!
What is happening is the first -e is being interpreted as an option for echo (to enable escape sequences'
Usually, you'd do something like echo -- "-e", and it should print simply -e, but echo is happy to behave differently, and simply prints out -- -e as a whole string.
echo does not interpret -- to mean the end of options.
The solution to the problem could also be found in the man pages:
Due to shell aliases and built-in echo command, using an unadorned
echo interactively or in a script may get you different functionality
than that described here. Invoke it via env (i.e., env echo ...)
to avoid interference from the shell.
So something like this should work:
x=(-a 2);echo "${x[#]}";x=(-e 2 -e); env echo "${x[#]}"

echo "-e" doesn't print anything

I'm using GNU bash, version 3.00.15(1)-release (x86_64-redhat-linux-gnu). And this command:
echo "-e"
doesn't print anything. I guess this is because "-e" is one of a valid options of echo command because echo "-n" and echo "-E" (the other two options) also produce empty strings.
The question is how to escape the sequence "-e" for echo to get the natural output ("-e").
The one true way to print any arbitrary string:
printf "%s" "$vars"
This is a tough one ;)
Usually you would use double dashes to tell the command that it should stop interpreting options, but echo will only output those:
$ echo -- -e
-- -e
You can use -e itself to get around the problem:
$ echo -e '\055e'
-e
Also, as others have pointed out, if you don't insist on using the bash builtin echo, your /bin/echo binary might be the GNU version of the tool (check the man page) and thus understand the POSIXLY_CORRECT environment variable:
$ POSIXLY_CORRECT=1 /bin/echo -e
-e
There may be a better way, but this works:
printf -- "-e\n"
You could cheat by doing
echo "-e "
That would be dash, e, space.
Alternatively you can use the more complex, but more precise:
echo -e \\\\x2De
[root#scintia mail]# POSIXLY_CORRECT=1; export POSIXLY_CORRECT
[root#scintia mail]# /bin/echo "-e"
-e
[root#scintia mail]#
Another alternative:
echo x-e | sed 's/^x//'
This is the way recommended by the autoconf manual:
[...] It is often possible to avoid this problem using 'echo "x$word"', taking the 'x' into account later in the pipe.
After paying careful attention to the man page :)
SYSV3=1 /usr/bin/echo -e
works, on Solaris at least
I like that one using a herestring:
cat <<<"-e"
Another way:
echo -e' '
echo -e " \b-e"
/bin/echo -e
works, but why?
[resin#nevada ~]$ which echo
/bin/echo

Resources