I need to write a bash script that prints out its command line arguments in sorted order, one per line.
I wrote this script and it works fine, but is there any other way? Especially without outputting it to a file and sorting.
#!/bin/bash
for var in $*
do
echo $var >> file
done
sort file
rm file
Test run of the program:
$ script hello goodbye zzz aa
aa
goodbye
hello
zzz
You can pipe the for-loop to sort, or just do
printf '%s\n' "$#" | sort
#!/bin/bash
for var in "$#"; do
echo "$var"
done | sort
You want to use $# in quotes (instead of $*) to accommodate arguments with spaces, such as
script hello "goodbye, cruel world"
The pipe gets rid of the need for a temporary file.
Related
I have file as below
ABC v1.2.3
PQRS v4.5
XYZ v2.0
I want to read each the whole line and cut them into individual strings and pass that as an argument to another script. How can i achieve this using bash shell scripting.
Expected Output:
bash test.sh ABC v1.2.3
Read each line into an array first.
while read -r -a args; do
bash test.sh "${args[#]}"
done < args.txt
You can also do it with xargs command line:
<params xargs -l test.sh
And with a test.sh like:
echo $0
echo $1
echo $2
The output is:
./test.sh
ABC
v1.2.3
./test.sh
PQRS
v4.5
./test.sh
XYZ
v2.0
And there is an online tester: Tutorials point
Umm have you tried-
while read -r line ; do test.sh $line ; done < file
The while loop reads the file (indicated at the end with < file) line by line.
Then it's just a matter of using the line stored in $line as you need. In your case, as an argument to test.sh.
Why does cat exit a shell script, but only when it's fed by a pipe?
Case in point, take this shell script called "foobar.sh":
#! /bin/sh
echo $#
echo $#
cat $1
sed -e 's|foo|bar|g' $1
And a text file called "foo.txt" which contains only one line:
foo
Now if I type ./foobar.sh foo.txt on the command line, then I'll get this expected output:
1
foo.txt
foo
bar
However if I type cat foo.txt | ./foobar.sh then surprisingly I only get this output:
0
foo
I don't understand. If the number of arguments reported by $# is zero, then how can cat $1 still return foo? And, that being the case, why doesn't sed -e 's|foo|bar|g' $1 return anything since clearly $1 is foo?
This seems an awful lot like a bug, but I'm assuming it's magic instead. Please explain!
UPDATE
Based on the given answer, the following script gives the expected output, assuming a one-line foo.txt:
#! /bin/sh
if [ $# ]
then
yay=$(cat $1)
else
read yay
fi
echo $yay | cat
echo $yay | sed -e 's|foo|bar|g'
No, $1 is not "foo". $1 is
ie, undefined/nothing.
Unlike a programming language, variables in the shell are quite dumbly and literally replaced, and the resulting commands textually executed (well, sorta kinda). In this case, "cat $1" becomes just "cat ", which will take input from stdin. That's terribly convenient to your execution since you've kindly provided "foo" on stdin via your pipe!
See what's happening?
sed likewise will read from stdin, but is already on end of stream, so exits.
When you don't give an argument to cat, it reads from stdin. When $1 isn't given the cat $1 is the same as a simple cat, which reads the text you piped in (cat foo.txt).
Then the sed command runs, and same as cat, it reads from stdin because it has no filename argument. cat has already consumed all of stdin. There's nothing left to read, so sed quits without printing anything.
There exists a command foo which expects two arguments which are filenames and which prints some stuff on stdout.
I have a Bash script with two variables a and b holding two strings.
I wish to pass to foo two filenames where the contents of those files are a and b. I then want to store the stdout as a new variable c.
Following ad hoc Googling, the script would perhaps look something like:
a=...;
b=...;
c=`foo <($a) <($b)`;
What should it look like?
a=...
b=...
c=$(foo <(echo "$a") <(echo "$b"))
echo "$c"
Try using echo -e
a=...
b=...
c=$(foo <(echo -e "$a") <(echo -e "$b"))
echo -e "$c"
When we use shell expansion, it gives all the expanded word in one line. For example:
#!/bin/bash
data="Hello\ {World,Rafi}"
eval echo $data
This produces the following output:
Hello World Hello Rafi
Is it possible to output each line on a separate line like this?
Hello World
Hello Rafi
If I understand you right, you want to generate multiple words using brace expansion ({...}), then print each word on a separate line.
If you don't absolutely have to store "Hello\ {World,Rafi}" in a variable, you can do this with printf shell-builtin
printf "%s\n" "Hello "{Rafi,World}
Some explanation:
The format string (here: %s\n) is reused until all the arguments to printf is used up (Reference).
%s\n consumes 1 argument
"Hello "{Rafi,World} returns 2 words/arguments i.e. "Hello Rafi" and "Hello World"
So, this printf command is equivalent to
printf "%s\n%s\n" "Hello Rafi" "Hello World"
except you don't have to type all that up.
#!/bin/bash
data="Hello\ {World'\n',Rafi'\n',Kamal'\n'}"
eval echo -e "$data"
echo -e will evaluate newline characters.
Same as Antarus'a answer, except that echo has "-n". From http://unixhelp.ed.ac.uk/CGI/man-cgi?echo
-n do not output the trailing newline
#!/bin/bash
data="Hello\ {World,Rafi}'\n'"
eval echo -n -e "$data"
Actually, your problem is not the expansion but the echo command. Depending on your system, you might get what you want by
#!/bin/bash
data="Hello\ {World\\\\n,Rafi}"
eval echo -e "$data"
It is different solution but a very clean one.
#!/bin/bash
names="World Rafi"
for name in $names
do
echo Hello $name
done
Instead of using eval (which is dangerous and really not a good practice — see my comment in your post), another strategy would be to use an array. The following will do exactly what you want, in a clean and safe way:
data=( "Hello "{World,Rafi} )
printf "%s\n" "${data[#]}"
I noticed today Bash printf has a -v option
-v var assign the output to shell variable VAR rather than
display it on the standard output
If I invoke like this it works
$ printf -v var "Hello world"
$ printf "$var"
Hello world
Coming from a pipe it does not work
$ grep "Hello world" test.txt | xargs printf -v var
-vprintf: warning: ignoring excess arguments, starting with `var'
$ grep "Hello world" test.txt | xargs printf -v var "%s"
-vprintf: warning: ignoring excess arguments, starting with `var'
xargs will invoke /usr/bin/printf (or wherever that binary is installed on your system). It will not invoke bash's builtin function. And only a builtin (or sourcing a script or similar) can modify the shell's environment.
Even if it could call bash's builtin, the xargs in your example runs in a subsell. The subshell cannot modify it's parent's environment anyway. So what you're trying cannot work.
A few options I see if I understand your sample correctly; sample data:
$ cat input
abc other stuff
def ignored
cba more stuff
Simple variable (a bit tricky depending on what exactly you want):
$ var=$(grep a input)
$ echo $var
abc other stuff cba more stuff
$ echo "$var"
abc other stuff
cba more stuff
With an array if you want individual words in the arrays:
$ var=($(grep a input))
$ echo "${var[0]}"-"${var[1]}"
abc-other
Or if you want the whole lines in each array element:
$ IFS=$'\n' var=($(grep a input)) ; unset IFS
$ echo "${var[0]}"-"${var[1]}"
abc other stuff-cba more stuff
There are two printf's - one is a shell bultin and this is invoked if you just run printf and the other is a regular binary, usually /usr/bin/printf. The latter doesn't take a -v argument, hence the error message. Since printf is an argument to xargs here, the binary is run, not the shell bulitin. Additionally, since it's at the receiving end of a pipeline, it is run as a subprocess. Variables can only be inherited from parent to child process but not the other way around, so even if the printf binary could modify the environment, the change wouldn't be visible to the parent process. So there are two reasons why your command cannot work. But you can always do var=$(something | bash -c 'some operation using builtin printf').
Mat gives an excellent explanation of what's going on and why.
If you want to iterate over the output of a command and set a variable to successive values using Bash's sprintf-style printf feature (-v), you can do it like this:
grep "Hello world" test.txt | xargs bash -c 'printf -v var "%-25s" "$#"; do_something_with_formatted "$var"' _ {} \;