Overwrite last line on terminal - bash

My bash-script looks as following:
echo "Description:"
while [ $finishInput -eq 0 ]; do
read tmp
desc="$desc"$'\n'"$tmp"
if [ -z "$tmp" ]; then
finishInput="1"
fi
done
echo -n "Maintainer:"
read maintainer
It reads to the desc var until a empty line is passed. After that, i want to read in other stuff.
When executing my current script it looks like this:
Description:
Line 1
Line 2
Maintainer:
I would like to overwrite the last empty line with the "Maintainer:".
I searched for a solution but only found suggestions which were like
echo -n "Old line"
echo -e "\r new line"
which stays on the line and overwrites it. This is not possible in my case.

In your example you delete the text at the same line. When you want to return to the previous line use \e[1A, and to clear that line, use \e[K:
echo 'Old line'
echo -e '\e[1A\e[Knew line'
When you want to go N lines up, use \e[<N>A

Found a great guide on escape sequences and wanted to expand on some of the discussions here.
When you write out to a terminal, you move an invisible cursor around, much like you do when you write in any text editor. When using echo, it will automatically end the output with a new line character which moves the cursor to the next line.
$ echo "Hello" && echo " World"
Hello
World
You can use -n to prevent the new line and if you echo again after this, it will append it to the end of that line
$ echo -n "Hello" && echo " World"
Hello World
The cursor remains where it was so, on it's own, we can't use -n to overwrite the previous line, we need to move the cursor to the left. To do that we need to give it an escape sequence, which we let echo know we're going to use with -e and then move the cursor by providing a return carriage \r which puts the cursor at the beginning of the line.
$ echo -n "Hello" && echo -e "\rWorld"
World
That may look like it worked, but see what happens with
$ echo -n "A longer sentance" && echo -e "\rShort sentance"
Short sentancence
See the extra characters? Simply writing over the line only changes the characters where we wrote them.
To fix this, the accepted answer above uses the escape character \e[0K to erase everything after the cursor, after the cursor has moved left. i.e. \r move to beginning \e[0K erase to end.
$ echo -n "A longer sentance" && echo -e "\r\e[0KShort sentance"
Short sentance
Important \e to begin escape sequences works in zsh but not in sh and not necessarily in bash, however \033 works in all of them. If you want your script to work anywhere, you should preference \033
$ echo -n "A longer sentance" && echo -e "\r\033[0KShort sentance"
Short sentance
But escape characters can provide even more utility. For example \033[1A moves the cursor to the previous line so we don't need the -n on the previous echo:
$ echo "A longer sentance" && echo -e "\r\033[1A\033[0KShort sentance"
Short sentance
\r move to the beginning \033[1A move up \033[0K erase to the end
Finally, this is all a bit messy in my book, so you can turn this into a function:
overwrite() { echo -e "\r\033[1A\033[0K$#"; }
Using $# just puts all the parameters of the function into the string
$ echo Longer sentance && overwrite Short sentence
Short sentence

I built a function from Dennis Williamsons Comment:
function clearLastLine() {
tput cuu 1 && tput el
}
Thanks to Dennis Williamson

If you echo without the newline character echo -n "Something", you can use \r with your next echo to move the 'cursor' to the beginning of the line echo -e "\\rOverwrite something".
#!/bin/bash
CHECK_MARK="\033[0;32m\xE2\x9C\x94\033[0m"
echo -e "\n\e[4mDoing Things\e[0m"
echo -n "doing thing 1..."
sleep 1
echo -e "\\r${CHECK_MARK} thing 1 done"
Just be aware that if your new string is shorter that your old string, the tail of your old string will still be visible. Note the done.. in the gif above.

If you want to run a script in a loop and not blow up your scrollback, you can use the following pattern:
while sleep 10s; do
echo -n $(script)
echo -n -e "\e[0K\r"
done
Just replace the script command with your own.

#!/bin/bash
echo "Description:"
while test -z $finishInput; do
read -s tmp
desc="$desc"$'\n'"$tmp"
if [ -z "$tmp" ]; then
finishInput=1
else
echo $tmp
fi
#echo "fi="$finishInput;
done
echo -n "Maintainer:"
read maintainer
This solution avoids the empty line, but input is not echoed before the lines are complete.
Hint: My version of bash did not accept "[ $finishInput -eq 0 ]".

Related

How to make multiple search and replace in files via bash

i have script. In this script i made search and replace of words. Word by word until word 'end'. It is ok and it works. You can see body of my script:
#!/bin/bash
end=end
until [ "$first" = "$end" ];do
echo "please write first word";
read first
if grep -q "$first" *txt; then
echo "word is exists"
grep "$first" *txt
echo "please write second word";
read second
sed -i 's/'"$first"'/'"$second"'/g' *txt
else
echo "second word does not exists"
exit 1
fi
done
It works for me. I have in the result console, where I can endlessly loop words, but if i want to do something like this: How can i write multiple words in line.
For example: "dog" "cat" "fish"
And search and replace all of these words. How can do it? For example, if i need to replace on these words ("elephat" "mouse" "bird"). How can you do it?
I mean search and replace words, like arguments.
You just need a loop to process the arguments.
Assuming you run the script passing pairs of original replacement words (myscript.sh original_word1 replacement1 original_word2 replacement2 ...) it would be something like the following:
while [[ $# -gt 1 ]]
do
original="$1"
replacement="$2"
# your code for actually replacing $original with $replacement
shift # discard already processed original arg
shift # discard already processed replacement arg
done
Note that if the user passes a last original word without replacement the script will just ignore it
Your English is rough, but I think you want to be able to prompt for multiple words, and replace them with a new set?
The below code will let you run a program like replace_words one two three and then be prompted for a list of words to replace, e.g. 1 2 3. After that, it exits.
declare -a replace_list=( "$#" ) # get the replace list as passed arguments
echo -n "Enter words to replace with: "; read -ra sub_list
for ((i=0; i < "${#replace_list[#]}"; ++i)); do
if grep -q "${replace_list[$i]}" *txt; then
echo "first word is exists"
sed -i "s/${replace_list[$i]}/${sub_list[$i]}/g" *txt
else
echo "${replace_list[$i]} does not exists"
exit 1
fi
done

read first line of the file and compare it with a string in shell file

I need to read first line of a file and match it with a text. If the text matches, I need to do certain operation.
Problem is if command is unable to compare the variable with the string.
file_content=$(head -1 ${file_name})
echo $file_content
if [[ $file_content = 'No new data' ]]; then
echo "Should come here"
fi
echo $file_content
if [ "${file_content}" = "No new data" ]; then
echo "Should come here"
fi
The if block is not working. I tried all possible syntax of if. I think the value that I am capturing in line 1 has some issues. Please help.
You could use
case $file_content in
(*No new data*)
echo "No news is good news."
;;
(*)
echo "Yay, breaking news."
esac
This would ignore any leading/trailing white space. This has the benefit of not forking expensive sed commands to just remove a few characters. That's like delivering a few sugar cubes with a flat bed truck...
There was trailing spaces.
Used the command
file_content="$(echo -e "${file_content_init}" | sed -e 's/^[[:space:]]//' -e 's/[[:space:]]$//')"
and then file_content was trimmed string that I used to compare.

Making a script that transforms sentences to title case?

I have a command that I use to transform sentences to title case. It is inefficient to have to copy this command out of a text file, and then paste it into the terminal before then also pasting in the sentence I want converted. The command is:
echo "my text" | sed 's/.*/\L&/; s/[a-z]*/\u&/g'
How can I convert this to a script so I can just call something like the following from the terminal:
TitleCaseConverter "my text"
Is it possible to create such a script? Is it possible to make it work from any folder location?
Since bash's parameter expansion includes case modification, there's no need for sed. Just a short function:
tc() { set ${*,,} ; echo ${*^} ; }
Test (don't use quotes, since a title is typically no longer than a sentence, it shouldn't matter):
tc FOO bar
Output:
Foo Bar
Fancy version that avoids capitalizing some conjunctions, articles and such:
ftc() { set ${*,,} ; set ${*^} ; echo -n "$1 " ; shift 1 ; \
for f in ${*} ; do \
case $f in A|The|Is|Of|And|Or|But|About|To|In|By) \
echo -n "${f,,} " ;; \
*) echo -n "$f " ;; \
esac ; \
done ; echo ; }
Test:
ftc the last of the mohicans
Output:
The Last of the Mohicans
How about just wrapping it into a function in .bashrc or .bash_profile and source it from the current shell
TitleCaseConverter() {
sed 's/.*/\L&/; s/[a-z]*/\u&/g' <<<"$1"
}
or) if you want it pitch-perfect to avoid any sorts of trailing new lines from the input arguments do
printf "%s" "$1" | sed 's/.*/\L&/; s/[a-z]*/\u&/g'
Now you can source the file once from the command line to make the function available, do
source ~/.bash_profile
Now you can use it in the command line directly as
str="my text"
newstr="$(TitleCaseConverter "$str")"
printf "%s\n" "$newstr"
My Text
Also to your question,
How can I convert this to a script so I can just call something like the following from the terminal
Adding the function to one of the start-up files takes care of that, recommend adding it to .bash_profile more though.
TitleCaseConverter "this is stackoverflow"
This Is Stackoverflow
Update:
OP was trying to create a directory with the name returned from the function call, something like below
mkdir "$(TitleCaseConverter "this is stackoverflow")"
The key again here is to double-quote the command-substitution to avoid undergoing word-splitting by shell.
I don't have comment privs, but slight improvement to ManUnitedBloke's answer, this will handle contractions like "don't" and "who's".
echo "my text" | sed 's/.*/\L&/; s/[a-z']*/\u&/g'

sed with loop to replace certain fields in a file with delimiter :

How should I use the sed command to replace certain fields with delimiter : and run a check to make sure that the user's input can be found within the file & if it can't be found it will loop again.
main_menu #function main_menu
echo "1) choice 1"
echo "2) choice 2"
read choice #read user choice on which choice he wants
if [ $choice -eq 1 ]
then
edit_item #function
read $choice_e #read input
grep -iqs "$choice_e: " Item.txt && echo "item found" #search file to find match
while [[ ! ${choice_e} =~ ^([Item.txt])$ ]]; do #loop to find if input matches search
echo "New Title: " #input new
read choice_n
sed -i 's/^/"$choice_n"\t/' Item.txt #edit the item
done
edit_item
else
echo "error" #return user to input again
fi
The invocation of sed is flawed because of the single quotes mixed with double quotes:
sed -i 's/^/"$choice_n"\t/'
The single quotes mean that the $ (and double quotes) are not interpreted by the shell. What you're probably after is:
sed -i "s/^/$choice_n\t/"
Without knowing exactly which shell and version of sed you're using, it isn't clear whether the \t sequence will be translated to a tab or not. In Bash, you could use the ANSI C Quoting mechanism:
sed -i "s/^/$choice_n"$'\t'/
I don't see where your 'delimiter :' is coming into play at all.

How to get first character of variable

I'm trying to get the first character of a variable, but I'm getting a Bad substitution error. Can anyone help me fix it?
code is:
while IFS=$'\n' read line
do
if [ ! ${line:0:1} == "#"] # Error on this line
then
eval echo "$line"
eval createSymlink $line
fi
done < /some/file.txt
Am I doing something wrong or is there a better way of doing this?
-- EDIT --
As requested - here's some sample input which is stored in /some/file.txt
$MOZ_HOME/mobile/android/chrome/content/browser.js
$MOZ_HOME/mobile/android/locales/en-US/chrome/browser.properties
$MOZ_HOME/mobile/android/components/ContentPermissionPrompt.js
To get the first character of a variable you need to say:
v="hello"
$ echo "${v:0:1}"
h
However, your code has a syntax error:
[ ! ${line:0:1} == "#"]
# ^-- missing space
So this can do the trick:
$ a="123456"
$ [ ! "${a:0:1}" == "#" ] && echo "doesnt start with #"
doesnt start with #
$ a="#123456"
$ [ ! "${a:0:1}" == "#" ] && echo "doesnt start with #"
$
Also it can be done like this:
$ a="#123456"
$ [ "$(expr substr $a 1 1)" != "#" ] && echo "does not start with #"
$
$ a="123456"
$ [ "$(expr substr $a 1 1)" != "#" ] && echo "does not start with #"
does not start with #
Update
Based on your update, this works to me:
while IFS=$'\n' read line
do
echo $line
if [ ! "${line:0:1}" == "#" ] # Error on this line
then
eval echo "$line"
eval createSymlink $line
fi
done < file
Adding the missing space (as suggested in fedorqui's answer ;) ) works for me.
An alternative method/syntax
Here's what I would do in Bash if I want to check the first character of a string
if [[ $line != "#"* ]]
On the right hand side of ==, the quoted part is treated literally whereas * is a wildcard for any sequence of character.
For more information, see the last part of Conditional Constructs of Bash reference manual:
When the ‘==’ and ‘!=’ operators are used, the string to the right of the operator is considered a pattern and matched according to the rules described below in Pattern Matching
Checking that you're using the right shell
If you are getting errors such as "Bad substitution error" and "[[: not found" (see comment) even though your syntax is fine (and works fine for others), it might indicate that you are using the wrong shell (i.e. not Bash).
So to make sure you are using Bash to run the script, either
make the script executable and use an appropriate shebang e.g. #!/bin/bash
or execute it via bash my_script
Also note that sh is not necessarily bash, sometimes it can be dash (e.g. in Ubuntu) or just plain ol' Bourne shell.
Try this:
while IFS=$'\n' read line
do
if ! [ "${line:0:1}" = "#" ]; then
eval echo "$line"
eval createSymlink $line
fi
done < /some/file.txt
or you can use the following for your if syntax:
if [[ ! ${line:0:1} == "#" ]]; then
TIMTOWTDI ^^
while IFS='' read -r line
do
case "${line}" in
"#"*) echo "${line}"
;;
*) createSymlink ${line}
;;
esac
done < /some/file.txt
Note: I dropped the eval, which could be needed in some (rare!) cases (and are dangerous usually).
Note2: I added a "safer" IFS & read (-r, raw) but you can revert to your own if it is better suited. Note that it still reads line by line.
Note3: I took the habit of using always ${var} instead of $var ... works for me (easy to find out vars in complex text, and easy to see where they begin and end at all times) but not necessary here.
Note4: you can also change the test to : *"#"*) if some of the (comments?) lines can have spaces or tabs before the '#' (and none of the symlink lines does contain a '#')

Resources