Add line to Shell Script File - bash

How can I add a line to a shell script, at the very end of a function using bash?
Say the line I want to add is: echo "Well Hello There"; and my function is
function hello_there {
echo "Hi";
echo "Whats popping?";
}
The only real constant will be the name of the method hello_there, and the beginning and end brackets... but I need the line to go above the end bracket... and I do not know how many times I will need to be able to do this...

I would not attempt to dynamically change the source of a script. It's too dangerous. There are several alternatives. The one closest to your problem specification would probably be to write the function like this:
function hello_there {
echo "Hi";
echo "Whats popping?";
${HELLO_THERE_POSTCMD:-true}
}
If you want to change the behaviour of this function, for example to have it recursively erase your home directory, you could set the environment variable
export HELLO_THERE_POSTCOMD="rm -rf $HOME"
before calling your script.

You could look for the closing "}" and replace it with
printf '"Well Hello There"; \n}'
and insert a new line character after the semi colon, also use printf rather than echo

You can regenerate function code as following :
function hello_there {
echo "Hi";
echo "Whats popping?";
}
cmd='echo \"Well\ Hello\ There \"'
type hello_there | tr '\n' ' ' | sed -e "s/.*nction/function/g" | sed -e "s/[(|)]//g" | sed -e "s/\}/\; $cmd\;\}/g"

Related

How do I recursively replace part of a string with another given string in bash?

I need to write bash script that converts a string of only integers "intString" to :id. intString always exists after /, may never contain any other types (create_step2 is not a valid intString), and may end at either a second / or end of line. intString may be any 1-8 characters. Script needs to be repeated for every line in a given file.
For example:
/sample/123456/url should be converted to /sample/:id/url
and /sample_url/9 should be converted to /sampleurl/:id however /sample_url_2/ should remain the same.
Any help would be appreciated!
It seems like the long way around the problem to go recursive but then I don't know what problem you are solving. It seems like a good sed command like
sed -E 's/\/[0-9]{1,}/\/:id/g'
could do it in one shot, but if you insist on being recursive then it might go something like this ...
#!/bin/bash
function restring()
{
s="$1"
s="$(echo $s | sed -E 's/\/[0-9]{1,}/\/:id/')"
if ( echo $s | grep -E '\/[0-9]{1,}' > /dev/null ) ; then
restring $s
else
echo $s
exit
fi
echo $s
}
restring "$1"
now run it
$ ./restring.sh "/foo/123/bar/456/baz/45435/andstuff"
/foo/:id/bar/:id/baz/:id/andstuff

Replace an unknown string within a line using Bash

I want to be able to modify db001 with a string I pass into the command via CLI. At any given time db001 could be a different value so I can't just look for that value.
./myscript modify_db <new value>
myfile.txt
./myscript modify_db mynewdbname002
Before: database_node=db001.mydomain.local
After: database_node=mynewdbname002.mydomain.local
./myscript modify_db db003
Before: database_node=mynewdbname002.mydomain.local
After: database_node=db003.mydomain.local
You can use this sed command inside your script:
sed "s/^\(database_node=\)[^.]*/\1$1/" file
Example:
s='database_node=db001.mydomain.local'
repl() {
sed "s/^\(database_node=\)[^.]*/\1$1/" <<< "$s";
}
and call it as:
repl mynewdbname002
database_node=mynewdbname002.mydomain.local
repl db003
database_node=db003.mydomain.local
You could have a script like, just like below taking an input argument having the replacement value,
#!/bin/bash
perl -lpe "s/database_node=(\w+)/database_node=$1/g" file
and just do
./script.sh newdbname
Use the -i flag for in-place replacement and -i.bak for in-place replacement with a backup of your original file
perl -lpe -i.bak "s/database_node=(\w+)/database_node=$1/g" file
(or) with a simple bash function
function replaceFile() {
perl -lpe -i.bak "s/database_node=(\w+)/database_node=$1/g" file
}
I would avoid trying to produce the new state from the previous state and rather just use a template :
function modify_db() {
echo "database_node=$1.mydomain.local"
}
I use echo here for illustration but you should obviously do whatever you want to do with the "database_node=$1.mydomain.local".
Supposing it should modify the only line starting with database_node from a file db_conf after having printed the old value :
function modify_db() {
echo "Before: $(grep '^database_node=' db_conf)"
sed -i "s/^database_node=.*\.mydomain\.local/database_node=$1.mydomain.local/" db_conf
echo "After: $(grep '^database_node=' db_conf)"
}

bash: how can I read text in file into string, modify it and save it another file?

I have a file (imaginary):
test() {
echo ${%%NUMBER%%}
}
I need:
Read it into string variable
Replace %%NUMBER%% with number
Save it to another file, maintaining the multi-line architecture.
How I do it:
#!/bin/bash
# Full path of this script
FILE=`readlink -f "${BASH_SOURCE[0]}"`
# This directory path
DIR=`dirname "${FILE}"`
repl() {
STRING=$(cat $DIR/skel.txt)
STRING=$(echo $STRING | sed "s/%%NUMBER%%/$1/")
echo $STRING
}
# try it
repl 50 > ./out.sh
# tried as well:
# repl 50 | tee ./out.sh
# echo "$(repl 50)" > ./out.sh
# STRING=$(echo -e $STRING | sed "s/%%NUMBER%%/$1/")
But I always get everything in one line in out.sh file. Need it to stay multiline, as in source.
Thanks.
Try this:
repl() {
sed "s/%%NUMBER%%/$1/" "$DIR/skel.txt"
}
This is far more efficient, and won't eat your precious newlines.

How to shorten path parts between // in bash

I want my bash prompt paths to be shortened:
~/workspace/project/my-project
# Should be
~/w/p/my-project
This could be achieved by just shortening parts of the path string between // to just the first character.
Is there a way to do this for example in sed?
edit:
Thought someone else looking into this might find what I ended useful so I'm editing it here.
.bashrc:
dir_chomp () {
pwd | sed "s|^$HOME|~|" 2> /dev/null | sed 's:\(\.\?[^/]\)[^/]*/:\1/:g'
}
parse_git_branch() {
git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/'
}
export PS1="\[\033[32m\]\$(dir_chomp)\[\033[33m\]\$(parse_git_branch)\[\033[00m\] $ "
prompt examples (coloring doesn't show):
~/w/e/coolstuff (master) $
~/.c/A/Cache $
If you want to unconditionally shorten all path components, you can do it quite easily with sed:
sed 's:\([^/]\)[^/]*/:\1/:g'
If you want to also insert ~ at the beginning of paths which start with $HOME, you can add that to the sed command (although this naive version assumes that $HOME does not include a colon).
sed 's:^'"$HOME"':~:/;s:\([^/]\)[^/]*/:\1/:g'
A better solution is to use bash substitution:
short_pwd() {
local pwd=$(pwd)
pwd=${pwd/#$HOME/\~}
sed 's:\([^/]\)[^/]*/:\1/:g' <<<"$pwd"
}
With that bash function, you can then "call" it from your PS1 string:
$ PS1='$(short_pwd)\$ '
~/s/tmp$ PS1='\$ '
$
Use PROMPT_COMMAND to set your prompt dynamically each time it is displayed.
shorten_path () {
cwd=${PWD/workspace/w}
cwd=${cwd/project/p}
cwd=${cwd/$HOME/~}
PS1="$cwd "'\$ '
}
PROMPT_COMMAND=shorten_path
This replaces the use of \w escape with custom code to shorten the current working directory. It has the unfortunate side effect of replacing ~ with the name of your home directory, though, which is why the third line is necessary to put it back, if desired.
I use this to shorten to 3 caracters plus "..":
shortpath()
{
dir=${1%/*} && last=${1##*/}
res=$(for i in ${dir//\// } ; do echo -n "${i:0:3}../" ; done)
echo "/$res$last"
}
Version to short to one caracter:
shortpath()
{
dir=${1%/*} && last=${1##*/}
res=$(for i in ${dir//\// } ; do echo -n "${i:0:1}/" ; done)
echo "/$res$last"
}
And then:
export PS1="\$(shortpath \$(pwd)) $"

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

Resources