Shell Script msg() echo "${RED}$#${NOCOLOR}", What does it mean [duplicate] - shell

Sometimes I have a one-liner that I am repeating many times for a particular task, but will likely never use again in the exact same form. It includes a file name that I am pasting in from a directory listing. Somewhere in between and creating a bash script I thought maybe I could just create a one-liner function at the command line like:
numresults(){ ls "$1"/RealignerTargetCreator | wc -l }
I've tried a few things like using eval, using numresults=function..., but haven't stumbled on the right syntax, and haven't found anything on the web so far. (Everything coming up is just tutorials on bash functions).

Quoting my answer for a similar question on Ask Ubuntu:
Functions in bash are essentially named compound commands (or code
blocks). From man bash:
Compound Commands
A compound command is one of the following:
...
{ list; }
list is simply executed in the current shell environment. list
must be terminated with a newline or semicolon. This is known
as a group command.
...
Shell Function Definitions
A shell function is an object that is called like a simple command and
executes a compound command with a new set of positional parameters.
... [C]ommand is usually a list of commands between { and }, but
may be any command listed under Compound Commands above.
There's no reason given, it's just the syntax.
Try with a semicolon after wc -l:
numresults(){ ls "$1"/RealignerTargetCreator | wc -l; }

Don't use ls | wc -l as it may give you wrong results if file names have newlines in it. You can use this function instead:
numresults() { find "$1" -mindepth 1 -printf '.' | wc -c; }

You can also count files without find. Using arrays,
numresults () { local files=( "$1"/* ); echo "${#files[#]}"; }
or using positional parameters
numresults () { set -- "$1"/*; echo "$#"; }
To match hidden files as well,
numresults () { local files=( "$1"/* "$1"/.* ); echo $(("${#files[#]}" - 2)); }
numresults () { set -- "$1"/* "$1"/.*; echo $(("$#" - 2)); }
(Subtracting 2 from the result compensates for . and ...)

You can get a
bash: syntax error near unexpected token `('
error if you already have an alias with the same name as the function you're trying to define.

The easiest way maybe is echoing what you want to get back.
function myfunc()
{
local myresult='some value'
echo "$myresult"
}
result=$(myfunc) # or result=`myfunc`
echo $result
Anyway here you can find a good how-to for more advanced purposes

Related

why function ls { ls; } hangs in there?

The following shell function definition hangs on there in bash console (RHEL/Ubuntu) in Cygwin it just quits the terminal when it is invoked.
$ function ls { ls; }
$ ls
Any reason why this behavior is?
Your defined ls command is recursively calling itself rather than the previous ls command.
If you want to call the actual ls from your redefined one, you can simply use which to get the full path name, such as redefining ls to give you the long format:
function ls { $(which ls) -l; }
That's effectively the same as:
function ls { /bin/ls -l; }
which won't give you the problems your solution has with recursion.
Another option is to use
function ls { command ls -l; }
command will suppress shell function lookup and only allow for built-ins or programs on the path.
Builtins (like cd) are handled slightly differently to programs since they aren't actually located on the file system. In that case, you can use builtin, rather then which, to call the built-in version.
If you want to define a function in terms of something that may already be a function, that's a bit trickier. You can use declare -f to get the current definition, then manipulate that to create a new definition.
An example of this (though contrived) follows. Let's say you declare a function to show all text files:
pax> showtxt()
...> {
...> ls *.txt
...> }
and you now want to give it a pretty heading. Using declare -f showtxt, you can see it's definition:
pax> declare -f showtxt
showtxt ()
{
ls *.txt
}
Running that may result in the following output:
pax> showtxt
passwords.txt p0rnsites.txt results.txt
Now say you wanted to change it to give it a heading. You can capture the output of declare -f and modify it to make a script which will redefine the function thus:
pax> declare -f showtxt | awk '$1=="ls"{print "echo Text files:"}{print}' >tmp.sh
pax> cat tmp.sh
showtxt ()
{
echo Text files:
ls *.txt
}
You can see that you now have a modified function definition which, when run, will replace the function:
pax> . ./tmp.sh
pax> declare -f showtxt
showtxt ()
{
echo Text files:;
ls *.txt
}
And, when you run the new function, it's behaviour has changed:
pax> showtxt
Text files:
passwords.txt p0rnsites.txt results.txt
Now that contrived example isn't that useful since you probably could have typed in in yourself. Where this comes in handy is when the original function is more complex or the changes you want to make to it are many and varied.
You named your function ls. Now, this overrides any other function(s) which were named ls before. So, as a result, your function calls itself recursively infinitely...
The best idea is to use unique names for your functions, i.e., this works fine:
function myls { ls; }

Bash: iterate through files based on regex parameter

There are several posts about iterating through bash files like this:
count_files() {
count=0
for f in "filename_*.txt"
do
count=$(($count + 1))
done
echo "Current count:$count"
}
I need to pass in "filename_*.txt" as a param when calling the bash script. Like this:
$: count_files.sh "filename_*.txt"
$: count_files.sh "different_filename_*.txt"
This, however, only gets the first file:
count_files() {
count=0
for f in $1
do
count=$(($count + 1))
done
echo "Current count:$count"
}
How do I pass in a regex param and iterate through it?
NOTE: counting the files is just an example. If you have a simple way to do that, please share, but that's not the main question.
Inside count_files.sh script make sure you call function with quotes like this:
count_files "$1"
instead of:
count_files $1
Later will get you count=1 because wildcard will be expanded before function call to the first file name.

Parse files to get a specific constant and its integer to store into an array

I have several Exception classes like this one (is PHP, but it doesnt matter right now):
class FileNotFoundException extends OtherException {
const DEFAULT_FILE_NOT_FOUND_ERROR_CODE = 159;
public function __construct($code=self::DEFAULT_FILE_NOT_FOUND_ERROR_CODE) {
parent::__construct($code);
}
}
The Problem
I need to get the error codes from all files to store them into an array (the index is the error code, and the value is the constant name) to just print it with order.
Tips
All the files are .php files
The constant error codes are always like const DEFAULT_[A-Z_]_ERROR_CODE = [0-9]+;
I tried to be stored in an array like this array[159]="DEFAULT_FILE_NOT_FOUND_ERROR_CODE";
What have I done
What I tried to do --with my little bashscripting knowledge-- is a script that parses all this php exception files and gets only the constant DEFAULT_[...]_ERROR_CODE = "number";
This is my script.sh trying to get it:
#! /bin/bash
sed -n "s/const \(DEFAULT[A-Z_]*\) = \([0-9]*\);/$array[\2]=\1;/p" $1
And if I do this:
script.sh < FileNotFoundException.php
It outputs [159]=DEFAULT_FILE_NOT_FOUND_ERROR_CODE;
Then I tried to put a variable "array" in there, like this:
eval('$array(`sed -n "s/const \(DEFAULT[A-Z_]*\) = \([0-9]*\);/$array[\2]=\1;/p" $1`)')
and several other combinations, but with no success.
Why I'm posting this in stackOverflow
I wanted to know if is possible to solve it and how, or if there is other way easier to do it.
Thanks
Remove the "$" in front of array:
#! /bin/bash
sed -n "s/.*const \(DEFAULT[A-Z_]*\) = \([0-9]*\);.*/array[\2]=\1;/p" $1
Now it will output array[159]=DEFAULT_FILE_NOT_FOUND_ERROR_CODE; which can be eval'd to set array elements:
eval "$(find yourdir -name '*.php' -exec cat {} + | yourscript)"
(I also added some .*s to the sed expression so that appending a $(rm -ri /) to the line in the php script won't cause you to evaluate it.)

read stdin in function in bash script

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>
}

Assigning dynamic value to variable

how can I assign a dynamic value to variable? The simplest method I know about is by using a function. For example
fn(){
VAR=$VAL
}
VAL=value
fn
echo $VAR
will output
value
but I want something simpler, like
VAR=$VAL
VAL=value
echo $VAR
to output
value
What command should I use? Preferably to be compatible with dash.
Thanks!
UPDATE: Removed #!/bin/sh in connection to dash. Thank "Ignacio Vazquez-Abrams" for the explanation!
UPDATE 2: Adding the source for my script to better understand the situation.
INPUT=`dpkg -l|grep ^rc|cut -d' ' -f3`
filter(){
echo *$A*
}
for A in $INPUT;do find ~ -iname `filter`|grep ^$HOME/\\.|grep -iz --color $A;done
This script should help finding the remaining configuration files of removed packages.
Okay, if function is not good, then maybe calling eval is okay?
export VAR='echo $VAL'
VAL=10
eval $VAR
This will display
10
How about a simple function that sets value?
# export is needed so f() can use it.
export VAR
f() {
VAR=$#
}
f 10
echo $VAR
f 20
echo $VAR
The code above will display:
10
20
If I understand your needs, you want an indirection, so try the following shell code (tested with dash) :
var=foo
x=var
eval $x=another_value
echo $var
output :
another_value
finally:
Each times you need to modify var, you need to run eval $x=foobar

Resources