I have some prompt set in oh-my-zsh theme which include some 'curl/wget' commands which obtain weather status. But every time I start the shell, prompt just waiting like 9-10 seconds before welcome me. What I'd like to do is to be able to set 'dynamic' prompt: default prompt without weather shows up momentarily when I start shell, then in 'background' weather is obtained and added to prompt (for example, when some command was executed and another one prompt shown). How can I do that?
UPDATE: I decided to go with cron job which will fetch weather simply every 5 minutes and then I can cat it not only to zsh prompt, but to any system part I want (for example, to WM statusbar). Although for people who are looking for answer on my exact question I'd recommend to try something like James Andrews proposed.
In your .zshrc file you could set
# How long to wait before calling TRAPALRM()
TMOUT=300
# called when TMOUT reaches 0
TRAPALRM()
{
# run this command in the background so my shell resets
{
export WEATHER=$(...)
}&!
}
And your prompt could use the $WEATHER variable.
Couple of ideas:
use the curl --max-time option to limit the damage.
have a cron job that runs every 5 minutes or so, fetch and write the weather to a file, your prompt can read the file.
Related
Is there's a way to update READLINE_LINE while running a command, and update it again when the command is complete?
Looking at Bash builtins docs:
-x keyseq:shell-command
Cause shell-command to be executed whenever keyseq is entered. When shell-command is executed, the shell sets the READLINE_LINE variable to the contents of the Readline line buffer and the READLINE_POINT and READLINE_MARK variables to the current location of the insertion point and the saved insertion point (the mark), respectively. If the executed command changes the value of any of READLINE_LINE, READLINE_POINT, or READLINE_MARK, those new values will be reflected in the editing state.
It's not very explicit, perhaps it means that the prompt is updated only once, after the shell-command is complete?
For instance, if I add this to .bashrc
bind -x '"\C-g":foobar'
foobar()
{
READLINE_LINE="# working on it..."
sleep 10
READLINE_LINE="# foo bar done"
}
when I press Ctrl-g, whatever typed disappears, leaving the prompt empty for 10 seconds, until it shows # foo bar done. The terminal never shows # working on it.... (Bash 5.1, tested on ubuntu and macos).
Any suggestion about changing the prompt multiple times, e.g. to show some progress in case the command takes long?
I left a comment but I guess you were looking for a proper answer. In this case, the reported behaviour is completely normal as of mentioned in here.
Quoting the previous answer: Environment variables are heritable, not shareable.
When you run a command on the command line, it runs in its own process, being those processes children of the shell process in which they run. That means that child processes can read all parent’s variables, but can‘t set new values back.
There are some exceptions to this behaviour, for example: printf, echo, kill, among others, which are built in commands for the given shell.
I want to print the date after every bash command I run.
This could help me understand how much a command took to execute when I am away from keyboard.
I know I could do
`DATE=`date +%d/%m/%Y\ %H:%M:%S` && echo $DATE`
to get the date but I don't know how or even if it could be possible to run this command after every command I execute on bash.
I would also be interested in running the same command before every command so I could know how long a command took.
Is it possible?
What file should I edit?
For example:
$ wget google.com
15/07/2017 23:40:05
I would be happy, if I could also introduce this following feauture:
$ wget google.com
15/07/2017 23:40:05 15/07/2017 23:40:11
Program run for 00:00:06
where the first date is when I ran the program, second is when program terminated the third is self-explanatonary.
As you understood, I don't want to type every time
$ wget google.com && `DATE=`date +%d/%m/%Y\ %H:%M:%S` && echo $DATE`
To execute a cmd before every command entered, set a trap on DEBUG. Eg.
trap date DEBUG
To execute that command before emitting a prompt, set PROMPT_COMMAND:
PROMPT_COMMAND=date
This does exactly that:
PROMPT_COMMAND+=$'\n'"date +%d/%m/%Y\ %H:%M:%S"
The string in PROMPT_COMMAND gets evaluated after every command. You just need to add the date command to whatever you already had in it. ($'\n' (newline) is a somewhat more robust joiner than ; as two consecutive ; would give you a syntax error)
You can add date/time to your prompt, via PS1 variable. You could use date command, but it's more efficient to use the supported special characters, like \d for date, or \D{strftime-fmt}.
For example:
PS1='\u#\h[\D{%F} \D{%T}]\w\$ '
or, with color:
PS1='\[\033[01;32m\]\u#\h\[\033[00m\][\[\033[02;33m\]\D{%F}\[\033[08m\]T\[\033[00m\]\[\033[02;33m\]\D{%T}\[\033[00m\]]\[\033[01;34m\]\w\[\033[00m\]\$ '
will show:
user#host[2017-07-16 00:01:17]~/somedir$
Note that in the second case (with color) we have a valid ISO8601 timestamp, with a "hidden" date/time separator T in the middle. If you select it with a mouse, T is visible and can be copied. (Also double-click will select the complete timestamp, not only date or time.)
To print timestamp after every command just modify your PS1 prompt and add date to it. The only catch here is that it will tell you time when command ended and new prompt showed. So in case you have your prompt open for long time just hit enter to capture start time before running your command.
PS1="\D{%F %T} \$ "
See this arch wiki page or just google bash prompt customization.
To add time spent executing program just add time before the command
$ time wget google.com
It will give you output like this
real 0m0.177s
user 0m0.156s
sys 0m0.020s
And you can get even more lazy and for commands that you dont't feel like typing time every time you run it, just create alias.
alias wget="time wget"
Because in bash aliases are run before other commands you can do it this way even if it looks like recursion. Then you will call it as you are used to.
And of course, aliases and prompt settings can be put in your .bashrc file, so you don't have to type them every time you open terminal.
I have this script
#!/bin/sh
curl -4 http://wttr.in/Colorado\ Springs
that I want to automatically execute each morning. I have my crontab entry as
* 7 * * * (path to script)
But either the script doesnt run, or it runs and then immediately closes the shell. I know that my cronjobs are running as I have other scripts for backups that run on an hourly basis but cant figure out what detail I am missing here. I found one suggestion to include $SHELL in the script but that made no difference. Any suggestions?
Usually when I have to keep the terminal open I would exec bash as my last command. I do that when I write installer script which would open terminal; do the job and get lost after that. But if there is an error then I want the terminal to stay there so that I can read the error.
exec is used to replace the current program with argument which we provide to exec.
Actually, I don't know what are you trying to achieve with this call in your crontab. Do you want to see the weather report on your terminal? Do you want to save the weather report in the file? Get it in your emails?
If you do no redirections, you'll get the report in your mail.
If you want to have it in a file, just do:
curl wttr.in/Colorado+Springs > file
If you want to have it on you terminals do
curl wttr.in/Colorado+Springs | wall
Please note that you don't need -4, http:// and you can replace \space with +.
(DISCLAIMER: I'm the author of wttr.in)
I would like to create a command alarm which plays a song at a given time. Here is the bash file, alarm, I've unfortunately created:
#!/bin/bash
at $1
open /.../mysongs/sweetsong.mp3
but when I run alarm 0830 in my terminal, a job is created and the song immediately starts playing. Then when the time actually comes for the job to run, nothing happens. I've come to the conclusion that it comes down to my use of at. Any tips? Thanks!
According to man at:
The at utility shall read commands from standard input and group them together as an at-job, to be executed at a later time.
So you need to pass the command in to standard input (and Use More Quotes™):
at "$1" < 'open /.../mysongs/sweetsong.mp3'
Hi. I'm new to the shell and am working on my first kludged together script. I've read all over the intertube and SO and there are many, MANY places where disown, nohup, & and return are explained but something isn't working for me.
I want a simpler timer. The script asks for user input for the hours, mins., etc., then:
echo "No problem, see you then…"
sleep $[a*3600+b*60+c]
At this point (either on the first or second lines, not sure) I want the script OR the specific command in the script to become a background process. Maybe a daemon? So that the timer will still go off on schedule even if
that terminal window is shut
the terminal app is quit completely
the computer is put to sleep (I realize I probably need some different code still to wake the mac itself)
Also after the "No problem" line I want a return command so that the existing shell window is still useful in the meantime.
The terminal-notifier command (the timer wakeup) is getting called immediately under certain usage of the above (I can't remember which right now), then a second notification at the right time. Using the return command anywhere basically seems to quit the script.
One thing I'm not clear on is whether/how disown, nohup, etc. are applicable to a command process vs. a script process, i.e., will any of them work properly on only a command inside a script (and if not, how to initialize a script as a background process that still asks for input).
Maybe I should use some alternative to sleep?
It isn't necessary to use a separate script or have the script run itself in order to get part of it to run in the background.
A much simpler way is to place the portions that you want to be backgrounded (the sleep and following command) inside of parentheses, and put an ampersand after them.
So the end of the script would look like:
(
sleep $time
# Do whatever
)&
This will cause that portion of the code to be run inside a subshell which is placed into the background, since there's no code after that the first shell will immediately exit returning control to your interactive shell.
When your script is run, it is actually run by starting a new shell to execute it. In order for you to get your script into the background, you would need to send that shell into the background, which you can't do because you would need to communicate with its parent shell.
What you can do is have your script call itself with a special argument to indicate that it should do the work:
#! /bin/zsh
if [ "$1" != '--run' ] ; then
echo sending to background
$0 --run $# &
exit
fi
sleep 1
echo backgrounded $#
This script first checks to see if its first argument is --run. If it is not, then it calls itself ($0) with that argument and all other arguments it received ($#) in the background, and exits. You can use a similar method, performing the test when you want to enter the background, and possibly sending the data you will need instead of every argument. For example, to send just the number of seconds:
$0 --run $[a*3600+b*60+c] &