How to capitalize first letter in bash? [duplicate] - bash

This question already has answers here:
uppercase first character in a variable with bash
(17 answers)
Closed 2 years ago.
editAppsDotPy() {
echo 'from django.apps import AppConfig' >> apps.py
echo >> apps.py
echo >> apps.py
echo "class ${APP_NAME}Config(AppConfig):" >> apps.py
echo " name = '${APP_NAME}'" >> apps.py
}
How would you capitalize the variable in the 5th line?
I was trying to do it with ${APP_NAME^} but it returns me an error.

Your function rewritten to work with more various shells:
script.sh:
#!/usr/bin/env sh
capitalize()
{
printf '%s' "$1" | head -c 1 | tr [:lower:] [:upper:]
printf '%s' "$1" | tail -c '+2'
}
editAppsDotPy()
{
cat >> 'app.py' <<EOF
from django.apps import AppConfig
class $(capitalize "$APP_NAME")Config(AppConfig):
name = '$APP_NAME'
EOF
}
APP_NAME='foo'
editAppsDotPy
Demoing:
sh script.sh
cat app.py
Output:
from django.apps import AppConfig
class FooConfig(AppConfig):
name = 'foo'

Assuming that tr is in your path, the more common parameter substitutions can help you too.
Your fifth line could look like the following:
echo "class `tr [:lower:] [:upper:] <<<${APP_NAME:0:1}`${APP_NAME:1}Config(AppConfig):" >> apps.py
I also tested this in zsh 5.8.

If your version of bash is too old to support that extension (Like the OS X version), or you're using a shell like zsh that doesn't support it at all, you have to turn to something else. Personally, I like perl (Which I think OS X comes with):
$ perl -ne 'print ucfirst' <<<"foobar"
Foobar
or for something in the middle of a longer string:
$ foo=bar
$ echo "foo='$(perl -ne 'print ucfirst' <<<"$foo")'"
foo='Bar'
which works in bash and zsh.

Related

How to preserve presence of quote marks from input with sed?

I have a short bash script to replace a uuid in a line in a file:
#!/bin/sh
alpha="0-9A-F"
uuidPtn="[$alpha]{8}-[$alpha]{4}-[$alpha]{4}-[$alpha]{4}-[$alpha]{12}"
ProductCode="\"ProductCode\" = \"8:{0059DDB5-D384-46F9-BBFD-0004A8C39732}\""
newguid=`uuidgen`
newguid="${newguid^^}"
cmd="echo $ProductCode | sed -r s/$uuidPtn/$newguid/"
echo "$ProductCode"
eval "$cmd"
It produces almost correct output, but with the quotation marks omitted:
"ProductCode" = "8:{0059DDB5-D384-46F9-BBFD-0004A8C39732}"
ProductCode = 8:{A4B1D092-1C56-44F3-B096-34B67A5F39B1}
How can I include the quotation marks?
Glad you got it working! Here's another way, which does not involve eval (since eval is evil):
#!/bin/bash
alpha="0-9A-F"
uuidPtn="[$alpha]{8}-[$alpha]{4}-[$alpha]{4}-[$alpha]{4}-[$alpha]{12}"
ProductCode="\"ProductCode\" = \"8:{0059DDB5-D384-46F9-BBFD-0004A8C39732}\""
newguid=`uuidgen`
newguid="${newguid^^}"
#cmd="echo "$ProductCode" | sed -r s/$uuidPtn/$newguid/" ## Not this
echo "$ProductCode"
#eval "$cmd" ## Not this either
# v v whole pattern quoted
changedcode=$(sed -r "s/$uuidPtn/$newguid/" <<<"$ProductCode")
# ^^ command substitution ^
# here-strings for input ^^^^^^^^^^^^^^^^^
echo "$changedcode"
Output:
"ProductCode" = "8:{0059DDB5-D384-46F9-BBFD-0004A8C39732}"
"ProductCode" = "8:{6094CF73-E23E-4655-B4A8-DAA57BE7EF72}"
This is a sh version
#!/bin/sh
alpha="0-9A-F"
uuidPtn="[$alpha]{8}-[$alpha]{4}-[$alpha]{4}-[$alpha]{4}-[$alpha]{12}"
ProductCode="\"ProductCode\" = \"8:{0059DDB5-D384-46F9-BBFD-0004A8C39732}\""
newguid=`uuidgen`
newguid=$(echo "${newguid}" | tr a-z A-Z)
ChangedCode=$(echo "$ProductCode" | sed -r s/$uuidPtn/$newguid/)
echo "$ProductCode"
echo "$ChangedCode"
I solved my own problem by changing the cmd= line to this:
cmd='echo $ProductCode | sed -r "s/$uuidPtn/$newguid/"'
thanks for the eyes on though folks.

bash function - variable value ignored [duplicate]

This question already has answers here:
How to use shell variables in perl command call in a bash shell script?
(6 answers)
Closed 6 years ago.
I am creating a kind-of alias for fast base64 encoding of strings. For it I have created following function and added it to my .bash_profile file:
# My functions
function b64() {
perl -MMIME::Base64 -e 'print encode_base64("$1");'
}
The problem is that it encodes the string "$1" itself without processing actual value that I am "giving" to it in request:
$ b64 "test_value"
JDE=
$ echo -n "JDE=" | base64 -d
$1
I have tried using '$1' and "$1", without any quotes, but the problem persists still and it keeps encoding $1 as string and not a value.
Could you please check what am I missing here?
Thanks in advance!
Apart from the obvious quoting problem that prevents the expansion of $1, you shouldn't inject data like so in your program: you should treat data as data!
Now, I'm no Perl expert, but the following should be more robust:
b64() {
perl -MMIME::Base64 -e 'print encode_base64($ARGV[0]);' -- "$1"
}
You are using the wrong kind of quotes. You can debug this more easily if you use echo to show what you're executing:
$ b64() { echo 'print encode_base64("$1");'; }
$ b64 foo
print encode_base64("$1");
$ b64() { echo "print encode_base64('$1');"; }
$ b64 foo
print encode_base64('foo');
Other debugging techniques exist - for example printf '%q\n' or set -x.
With this knowledge, you can write your b64 as
b64() { perl -MMIME::Base64 -e "print encode_base64('$1');"; }
This gives me the expected result:
$ b64 foo
Zm9v
$ base64 -d <<<Zm9v
foo
You're missing the fact that single quotes inhibit expansion.
perl -MMIME::Base64 -e 'print encode_base64("'"$1"'");'

Substitution with sed + bash function

my question seems to be general, but i can't find any answers.
In sed command, how can you replace the substitution pattern by a value returned by a simple bash function.
For instance, I created the following function :
function parseDates(){
#Some process here with $1 (the pattern found)
return "dateParsed;
}
and the folowing sed command :
myCatFile=`sed -e "s/[0-3][0-9]\/[0-1][0-9]\/[0-9][0-9]/& parseDates &\}/p" myfile`
I found that the caracter '&' represents the current pattern found, i'd like it to be passed to my bash function and the whole pattern to be substituted by the pattern found +dateParsed.
Does anybody have an idea ?
Thanks
you can use the "e" option in sed command like this:
cat t.sh
myecho() {
echo ">>hello,$1<<"
}
export -f myecho
sed -e "s/.*/myecho &/e" <<END
ni
END
you can see the result without "e":
cat t.sh
myecho() {
echo ">>hello,$1<<"
}
export -f myecho
sed -e "s/.*/myecho &/" <<END
ni
END
Agree with Glenn Jackman.
If you want to use bash function in sed, something like this :
sed -rn 's/^([[:digit:].]+)/`date -d #&`/p' file |
while read -r line; do
eval echo "$line"
done
My file here begins with a unix timestamp (e.g. 1362407133.936).
Bash function inside sed (maybe for other purposes):
multi_stdin(){ #Makes function accepet variable or stdin (via pipe)
[[ -n "$1" ]] && echo "$*" || cat -
}
sans_accent(){
multi_stdin "$#" | sed '
y/àáâãäåèéêëìíîïòóôõöùúûü/aaaaaaeeeeiiiiooooouuuu/
y/ÀÁÂÃÄÅÈÉÊËÌÍÎÏÒÓÔÕÖÙÚÛÜ/AAAAAAEEEEIIIIOOOOOUUUU/
y/çÇñÑߢÐð£Øø§µÝý¥¹²³ªº/cCnNBcDdLOoSuYyY123ao/
'
}
eval $(echo "Rogério Madureira" | sed -n 's#.*#echo & | sans_accent#p')
or
eval $(echo "Rogério Madureira" | sed -n 's#.*#sans_accent &#p')
Rogerio
And if you need to keep the output into a variable:
VAR=$( eval $(echo "Rogério Madureira" | sed -n 's#.*#echo & | desacentua#p') )
echo "$VAR"
do it step by step. (also you could use an alternate delimiter , such as "|" instead of "/"
function parseDates(){
#Some process here with $1 (the pattern found)
return "dateParsed;
}
value=$(parseDates)
sed -n "s|[0-3][0-9]/[0-1][0-9]/[0-9][0-9]|& $value &|p" myfile
Note the use of double quotes instead of single quotes, so that $value can be interpolated
I'd like to know if there's a way to do this too. However, for this particular problem you don't need it. If you surround the different components of the date with ()s, you can back reference them with \1 \2 etc and reformat however you want.
For instance, let's reverse 03/04/1973:
echo 03/04/1973 | sed -e 's/\([0-9][0-9]\)\/\([0-9][0-9]\)\/\([0-9][0-9][0-9][0-9]\)/\3\/\2\/\1/g'
sed -e 's#[0-3][0-9]/[0-1][0-9]/[0-9][0-9]#& $(parseDates &)#' myfile |
while read -r line; do
eval echo "$line"
done
You can glue together a sed-command by ending a single-quoted section, and reopening it again.
sed -n 's|[0-3][0-9]/[0-1][0-9]/[0-9][0-9]|& '$(parseDates)' &|p' datefile
However, in contrast to other examples, a function in bash can't return strings, only put them out:
function parseDates(){
# Some process here with $1 (the pattern found)
echo dateParsed
}

Pipe string with newline to command in bash?

I am trying to pass in a string containing a newline to a PHP script via BASH.
#!/bin/bash
REPOS="$1"
REV="$2"
message=$(svnlook log $REPOS -r $REV)
changed=$(svnlook changed $REPOS -r $REV)
/usr/bin/php -q /home/chad/www/mantis.localhost/scripts/checkin.php <<< "${message}\n${changed}"
When I do this, I see the literal "\n" rather than the escaped newline:
blah blah issue 0000002.\nU app/controllers/application_controller.rb
Any ideas how to translate '\n' to a literal newline?
By the way: what does <<< do in bash? I know < passes in a file...
try
echo -e "${message}\n${changed}" | /usr/bin/php -q /home/chad/www/mantis.localhost/scripts/checkin.php
where -e enables interpretation of backslash escapes (according to man echo)
Note that this will also interpret backslash escapes which you potentially have in ${message} and in ${changed}.
From the bash manual:
Here Strings
A variant of here documents, the format is:
<<<word
The word is expanded and supplied to the command on its standard input.
So I'd say
the_cmd <<< word
is equivalent to
echo word | the_cmd
newline=$'\n'
... <<< "${message}${newline}${changed}"
The <<< is called a "here string". It's a one line version of the "here doc" that doesn't require a delimiter such as "EOF". This is a here document version:
... <<EOF
${message}${newline}${changed}
EOF
in order to avoid interpretation of potential escape sequences in ${message} and ${changed}, try concatenating the strings in a subshell (a newline is appended after each echo unless you specify the -n option):
( echo "${message}" ; echo "${changed}" ) | /usr/bin/php -q /home/chad/www/mantis.localhost/scripts/checkin.php
The parentheses execute the commands in a subshell (if no parentheses were given, only the output of the second echo would be piped into your php program).
It is better to use here-document syntax:
cat <<EOF
copy $VAR1 $VAR2
del $VAR1
EOF
You can use magical Bash $'\n' with here-word:
cat <<< "copy $VAR1 $VAR2"$'\n'"del $VAR1"
or pipe with echo:
{ echo copy $VAR1 $VAR2; echo del $VAR1; } | cat
or with printf:
printf "copy %s %s\ndel %s" "$VAR1" "$VAR2" "$VAR1" | cat
Test it:
env VAR1=1 VAR2=2 printf "copy %s %s\ndel %s" "$VAR1" "$VAR2" "$VAR1" | cat

Splitting /proc/cmdline arguments with spaces

Most scripts that parse /proc/cmdline break it up into words and then filter out arguments with a case statement, example:
CMDLINE="quiet union=aufs wlan=FOO"
for x in $CMDLINE
do
»···case $x in
»···»···wlan=*)
»···»···echo "${x//wlan=}"
»···»···;;
»···esac
done
The problem is when the WLAN ESSID has spaces. Users expect to set wlan='FOO
BAR' (like a shell variable) and then get the unexpected result of 'FOO with the above code, since the for loop splits on spaces.
Is there a better way of parsing the /proc/cmdline from a shell script falling short of almost evaling it?
Or is there some quoting tricks? I was thinking I could perhaps ask users to entity quote spaces and decode like so: /bin/busybox httpd -d "FOO%20BAR". Or is that a bad solution?
There are some ways:
cat /proc/PID/cmdline | tr '\000' ' '
cat /proc/PID/cmdline | xargs -0 echo
These will work with most cases, but will fail when arguments have spaces in them. However I do think that there would be better approaches than using /proc/PID/cmdline.
set -- $(cat /proc/cmdline)
for x in "$#"; do
case "$x" in
wlan=*)
echo "${x#wlan=}"
;;
esac
done
Most commonly, \0ctal escape sequences are used when spaces are unacceptable.
In Bash, printf can be used to unescape them, e.g.
CMDLINE='quiet union=aufs wlan=FOO\040BAR'
for x in $CMDLINE; do
[[ $x = wlan=* ]] || continue
printf '%b\n' "${x#wlan=}"
done
Since you want the shell to parse the /proc/cmdline contents, it's hard to avoid eval'ing it.
#!/bin/bash
eval "kernel_args=( $(cat /proc/cmdline) )"
for arg in "${kernel_args[#]}" ; do
case "${arg}" in
wlan=*)
echo "${arg#wlan=}"
;;
esac
done
This is obviously dangerous though as it would blindly run anything that was specified on the kernel command-line like quiet union=aufs wlan=FOO ) ; touch EVIL ; q=( q.
Escaping spaces (\x20) sounds like the most straightforward and safe way.
A heavy alternative is to use some parser, which understand shell-like syntax.
In this case, you may not even need the shell anymore.
For example, with python:
$ cat /proc/cmdline
quiet union=aufs wlan='FOO BAR' key="val with space" ) ; touch EVIL ; q=( q
$ python -c 'import shlex; print shlex.split(None)' < /proc/cmdline
['quiet', 'union=aufs', 'wlan=FOO BAR', 'key=val with space', ')', ';', 'touch', 'EVIL', ';', 'q=(', 'q']
Use xargs -n1:
[centos#centos7 ~]$ CMDLINE="quiet union=aufs wlan='FOO BAR'"
[centos#centos7 ~]$ echo $CMDLINE
quiet union=aufs wlan='FOO BAR'
[centos#centos7 ~]$ echo $CMDLINE | xargs -n1
quiet
union=aufs
wlan=FOO BAR
[centos#centos7 ~]$ xargs -n1 -a /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-3.10.0-862.14.4.el7.x86_64
root=UUID=3260cdba-e07e-408f-93b3-c4e9ff55ab10
ro
consoleblank=0
crashkernel=auto
rhgb
quiet
LANG=en_US.UTF-8
You could do something like the following using bash, which would turn those arguments in to variables like $cmdline_union and $cmdline_wlan:
bash -c "for i in $(cat /proc/cmdline); do printf \"cmdline_%q\n\" \"\$i\"; done" | grep = > /tmp/cmdline.sh
. /tmp/cmdline.sh
Then you would quote and/or escape things just like you would in a normal shell.
In posh:
$ f() { echo $1 - $3 - $2 - $4
> }
$ a="quiet union=aufs wlan=FOO"
$ f $a
quiet - wlan=FOO - union=aufs -
You can define a function and give your $CMDLINE unquoted as an argument to the function. Then you'll invoke shell's parsing mechanisms. Note, that you should test this on the shell it will be working in -- zsh does some funny things with quoting ;-).
Then you can just tell the user to do quoting like in shell:
#!/bin/posh
CMDLINE="quiet union=aufs wlan=FOO"
f() {
while test x"$1" != x
do
case $1 in
union=*) echo ${1##union=}; shift;;
*) shift;;
esac
done
}
f $CMDLINE
(posh - Policy-compliant Ordinary SHell, a shell stripped of any features beyond standard POSIX)
Found here a nice way to do it with awk, unfortunately it will work only with doublequotes:
# Replace spaces outside double quotes with newlines
args=`cat /proc/cmdline | tr -d '\n' | awk 'BEGIN {RS="\"";ORS="\"" }{if (NR%2==1){gsub(/ /,"\n",$0);print $0} else {print $0}}'`
IFS='
'
for line in $args; do
key=${line%%=*}
value=${line#*=}
value=`echo $value | sed -e 's/^"//' -e 's/"$//'`
printf "%20s = %s\n" "$key" "$value"
done

Resources