I am trying to write a function in bash that takes a space delimited string and outputs the 1st word in that string. I tried
function myFunc { a=$1; b=($a); echo ${b[0]};}
but with
myStr="hello world"
myFunc myStr
I get no output.
if I replace a=$1 in the function definition above with a="hello world", then I get the expected result on applying
myFunc
result: hello
Also if instead of passing myStr as an argument I pass in "hello world" directly, it also works:
myFunc "hello world"
result: hello
I tried all kinds of indirections with the exclamation points, as well as the expr constructions but to no avail. Finally the following seems to work:
function el_index { alias a=$1; b=($a); echo ${b[2]};}
I would like to gather here all other possible ways to accomplish the same thing as above. In particular, is there a way to bypass the intermediate variable a completely, and do something like b=(${$a}) in the second statement within the function body above? By the way, the construction
b=($a)
splits the string stored in a into words using the default delimiter, which is a single white space.
Thanks!
You don't "get no output" -- you do get the first word of the string you passed:
function myFunc { a=$1; b=($a); echo ${b[0]}; }
myStr="hello world"
myFunc myStr
myStr
If you're passing a variable name, you have to indirectly expand the variable in your function
function myFunc { a=${!1}; b=($a); echo ${b[0]}; }
# ..................^^^^^
myFunc myStr
hello
Here's another technique: it takes the variable name as the first parameter, then overwrites the positional parameters with the words in the value of the variable. The first word is then the first positional parameter
firstword() { set -- ${!1}; echo $1; }
firstword myStr
hello
You can get 1st word this way:
myStr="hello world"
echo "${myStr%% *}"
hello
And your function will be:
function myFunc { echo "${1%% *}"; }
This seems rather simple if I understand you correctly. I also think your method works.
function myFunc1 {
a="${1}"; b=( $a ); echo ${b[0]};
}
function myFunc2 {
echo "${1}" | awk '{ print $1 }'
}
function myFunc3 {
echo "${1%% *}"
}
myFunc1 "Hello World"
# Which prints out: Hello
myFunc2 "Hello World"
# Which prints out: Hello
myFunc3 "Hello World"
# Which prints out: Hello
Output:
./test.sh
Hello
Hello
Hello
Related
I am learning to play around with functions in bash. I have the first function read_file() that reads /etc/file and replaces ':'with a space between words (e.g root:x:0:0:root ... becomes root x 0 0 root ... ). I then want to be able to manipulate output from individual words in each of the lines.
My second function- display__user_shell() prints our the shell for each corresponding users as is in the /etc/file.
My problem is figuring out how to call the first function read_file() and using its variables in the display__user_shell function.
I have been able to do the above when using input from a single line rather than reading from a file.
i just called new_data -i.e $new_data from the display__user_shell() function
read_file() {
read -p "Enter file" file
while read line
do
newlin=$(echo $line | tr ":" " ")
echo newlin
done
}
oldIFS=$IFS
IFS=" "
ct=0
display__user_shell() {
readfile
for item in $newlin;
do
[ $ct -eq 0 ] && name="$item";
[ $ct -eq 6 ] && name="$item";
done
echo "$user's shell is $shell"
}
IFS=$oldIFS
display__user_shell
the first line of the output should be..
root's shell is /bin/bash
Irrespective of the implementation there is an interesting question here: how to reference variables from one function in another function. The short answer is that you can:
$ a() { aye=bee; }
$ b() { echo "$aye"; }
$ a
$ b
bee
But this is a very bad idea - Bash has "unfortunate" scoping rules different from safer languages like Java, Python, or Ruby, and code like this is very hard to follow. Instead there are several patterns you can use to produce more readable code:
Print the value in the inner function and assign that to a value in the outer function:
a() {
echo 'bee'
}
b() {
aye="$(a)"
echo "$aye"
}
b # Prints "bee"
Call and assign to a variable the first function in the outer scope and use it in the second function:
a() {
echo 'bee'
}
aye="$(a)"
b() {
echo "$aye"
}
b # Prints "bee"
Treat the first and second functions as a pipeline, passing standard output of the first one to the standard input of the second one (read is a slow way to process a large file, but it'll serve as an example):
a() {
echo 'bee'
}
b() {
while read -r line
do
echo "$line"
done
}
a | b # Prints "bee"
Which one you choose depends on things like what else you intend to do with what a returns and whether a produces huge amounts of output.
How can I use a bashrc command like below
# .bashrc file
# google_speech
say () {
google_speech -l en "$1"
}
as a string of text, since the above code only reads out the first word of the sentence or paragraph i paste.
like for example if i go into terminal and type:
$ say hello friends how are you
then the script only thinks i typed
$ say hello
Try to use "$#" (with double quotes arround) to get all the arguments of your function:
$ declare -f mysearch # Showing the definition of mysearch (provided by your .bashrc)
mysearch ()
{
echo "Searching with keywords : $#"
}
$ mysearch foo bar # execution result
Searching with keywords : foo bar
Function or scripts arguments are like arrays, so you can use:
1) $# / ${#array[#]} to getting the number of arguments / array's elements.
2) $1 / ${array[1]} to getting the first argument / array's element.
3) $# / ${array[#]} to getting all arguments / array's elements.
EDIT: according to chepner's comment:
Using $# inside a larger string can sometimes produce unintended results. Here, the intent is to produce a single word, so it would be better to use $*
And here's a good topic with great answers explaining the differences.
EDIT 2: Do not forget to put double quotes around $# or $*, maybe your google_speach is taking only one arg. Here's a demo to give you a better understanding:
$ mysearch () { echo "Searching with keywords : $1"; }
$ mysearch2 () { mysearch "$*"; }
$ mysearch2 Hello my dear
Searching with keywords : Hello my dear
$ mysearch3 () { mysearch $*; } # missing double quotes
$ mysearch3 Hello my dear
Searching with keywords : Hello # the other arguments are at least ignored (or sometimes the program will fail when he's controlling the args).
I am calling function from another script from awk.
Why i need to press enter even after adding /dev/null
Why the passed argument is not displayed. I am getting spaces.
I am not getting the return value from external function.
cat mainpgm.sh
#!/bin/bash
key="09"
awk -v dk="$key" ' { ma = system(". /home/ott/functions.sh;derived dk")</dev/null ; "print returned value" ma } '
cat functions.sh
#!/bin/bash
derived () {
echo "outside function" $dk
return 9
}
If you don't want to process input in awk, redirect its input to /dev/null, and do everything in the BEGIN block. Also, for the dk variable to be replaced with its value, it has to be outside the quotes.
awk -v dk="$key" 'BEGIN {
ma = system(". /home/ott/functions.sh;derived " dk);
print "returned value", ma
}' < /dev/null
To answer your questions:
You put /dev/null in the wrong place. It's supposed to be the input of the script, not the system function.
Two reasons: First, you put dk inside the quotes, so its value is not substituted. Second, the derived function doesn't print its argument, it prints $dk, which is a nonexistent shell variable. The argument to a function is $1, so it should do:
echo "outside function $1"
You had print inside the quotes, so it wasn't being executed as a statement.
I have some set of bash functions which output some information:
find-modelname-in-epson-ppds
find-modelname-in-samsung-ppds
find-modelname-in-hp-ppds
etc ...
I've been writing functions which read output and filter it:
function filter-epson {
find-modelname-in-epson-ppds | sed <bla-blah-blah>
}
function filter-hp {
find-modelname-in-hp-ppds | sed <the same bla-blah-blah>
}
etc ...
But the I thought that it would be better do something like this:
function filter-general {
(somehow get input) | sed <bla-blah-blah>
}
and then call in another high-level functions:
function high-level-func {
# outputs filtered information
find-modelname-in-hp/epson/...-ppds | filter-general
}
How can I achieve that with the best bash practices?
If the question is How do I pass stdin to a bash function?, then the answer is:
Shellscript functions take stdin the ordinary way, as if they were commands or programs. :)
input.txt:
HELLO WORLD
HELLO BOB
NO MATCH
test.sh:
#!/bin/sh
myfunction() {
grep HELLO
}
cat input.txt | myfunction
Output:
hobbes#metalbaby:~/scratch$ ./test.sh
HELLO WORLD
HELLO BOB
Note that command line arguments are ALSO handled in the ordinary way, like this:
test2.sh:
#!/bin/sh
myfunction() {
grep "$1"
}
cat input.txt | myfunction BOB
Output:
hobbes#metalbaby:~/scratch/$ ./test2.sh
HELLO BOB
To be painfully explicit that I'm piping from stdin, I sometimes write
cat - | ...
A very simple means to get stdin into a variable is to use read. By default, it reads file descriptor "0", i.e. stdin i.e., /dev/stdin.
Example Function:
input(){ local in; read in; echo you said $in; }
Example implementation:
echo "Hello World" | input
Result:
you said Hello World
Additional info
You don't need to declare a variable as being local, of course. I just included that for the sake of good form. Plain old read in does what you need.
So you understand how read works, by default it reads data off the given file descriptor (or implicit stdin) and blocks until it encounters a newline. Much of the time, you'll find that will implicitly be attached to your input, even if you weren't aware of it. If you have a function that seems to hang with this mechanism just keep this detail in mind (there are other ways of using read to deal with that...).
More robust solutions
Adding on to the basic example, here's a variation that lets you pass the input via a stdin OR an argument:
input()
{
local in=$1; if [ -z "$in" ]; then read in; fi
echo you said $in
}
With that tweak, you could ALSO call the function like:
input "Hello World"
How about handling an stdin option plus other arguments? Many standard nix utilities, especially those which typically work with stdin/stdout adhere to the common practice of treating a dash - to mean "default", which contextually means either stdin or stdout, so you can follow the convention, and treat an argument specified as - to mean "stdin":
input()
{
local a=$1; if [ "$a" == "-" ]; then read a; fi
local b=$2
echo you said $a $b
}
Call this like:
input "Hello" "World"
or
echo "Hello" | input - "World"
Going even further, there is actually no reason to only limit stdin to being an option for only the first argument! You might create a super flexible function that could use it for any of them...
input()
{
local a=$1; if [ "$a" == "-" ]; then read a; fi
local b=$2; if [ "$b" == "-" ]; then read b; fi
echo you said $a $b
}
Why would you want that? Because you could formulate, and pipe in, whatever argument you might need...
myFunc | input "Hello" -
In this case, I pipe in the 2nd argument using the results of myFunc rather than the only having the option for the first.
Call sed directly. That's it.
function filter-general {
sed <bla-blah-blah>
}
I want to pass an array parameter to a function in bash, and writing some testing code as:
#!/bin/sh
function foo {
a=$1;
for i in ${a[#]} ; do
echo $i
done
}
names=(jim jerry jeff)
foo ${names[#]}
the above code just show jim, rather than the three j*. so my question is:
why my code doesn't work
what's the right way to do it
#!/bin/bash
function foo {
a=($*)
for i in ${a[#]}
do
echo $i
done
}
names=(jim jerry jeff)
foo ${names[#]}
Your code did not show jim to me, but "names", literally. You have to pass the whole array. And you have to recapture it with a=$($).
The manpage part in bash about Arrays is rather long. I only cite one sentence:
Referencing an array variable without a subscript is equivalent to referencing the array with a subscript of 0.
You're fairly close; the biggest problem was the command a=$1, which assigns only the first parameter ($1) to a, while you want to assign the entire list of parameters ($#), and assign it as an array rather than as a string. Other things I corrected: you should use double-quotes around variables whenever you use them to avoid confusion with special characters (e.g. spaces); and start the script with #!/bin/bash, since arrays are a bash extension, not always available in a brand-X shell.
#!/bin/bash
function foo {
a=("$#")
for i in "${a[#]}" ; do
echo "$i"
done
}
names=(jim jerry jeff "jim bob")
foo "${names[#]}"
For example like this:
my_array[0]="jim"
my_array[1]="jerry"
function foo
{
#get the size of the array
n=${#my_array[*]}
for (( Idx = 0; Idx < $n; ++Idx )); do
echo "${my_array[$Idx]}"
done
}