Proper formatting of Command Substitution - shell

I am trying to add the currently playing song if there is one to my ZSH prompt. I'm using a JXA command osascript -l JavaScript -e "Application('Music').currentTrack.name()". I am trying to assign it to a variable. and then echo that command.
precmd() {
SONG=$( echo -e osascript -l JavaScript -e "Application('Music').currentTrack.name()" )
LEFT=echo $SONG
RIGHT="$(dracula_time_segment) $(battery_pct_prompt)"
RIGHTWIDTH=$(($COLUMNS-${#LEFT}))
}
I've tried a number of variations eg: echo inside and outside the expression and various flags.

You don't need echo at all, as demonstrated by your later command substitutions when setting RIGHT; command substitution just takes a command and executes it.
SONG=$(osascript -l JavaScript -e "Application('Music').currentTrack.name()")
LEFT="$SONG"
You could combine the previous two commands; SONG isn't needed.
LEFT=$(osascript ...)

Related

Bash: Insert unescaped string/characters from variable into command

In bash (GNU bash, version 3.2.57), I would like to substitute the exact content of a variable (unescaped) into a following command.
To illustrate what I mean, given the following string variable:
s="2>&1 > /dev/null"
If I try to insert that exact string into a command:
bash --version $s || echo "will install bash"
(this command is just a simple example for the sake of the question)
The command actually executed looks like this:
bash --version '2>&1' '>' /dev/null
The inserted strings are escaped, which I don't want.
What I would like instead is to somehow insert the content of s, unescaped, into the executed command, so that the executed command is this one:
bash --version 2>&1 > /dev/null
How could I achieve that ?
How could I achieve that ?
Instead of a variable, use a function.
run_this_silent() {
"$#" 2>&1 >/dev/null
}
run_this_silent bash --version
It is not possible to store redirections in a variable without using eval (or the equivalent bash -c COMMAND), and eval is a bad solution in pretty much every case imaginable. If you want to unconditionally silence a command (or a hundred commands) it's better to explicitly add the redirects to each of them.

How do I execute a sed command from a Bash variable?

I have a sed command that works when executed directly.
echo foo > /home/user/bar
sed -i 's/foo/zoo/' /home/user/bar
It also works when directly embedded in $(...) or `...`.
However, if I try to execute it from a Bash variable, I get an error:
CMD="sed -i 's/foo/zoo/' /home/user/bar"
$CMD
Error:
sed: -e expression #1, char 1: unknown command: `''
It also works if I echo it out and source the file:
echo $CMD > file
source file
What's going on here, and how do I get the sed command to run from a Bash variable?
Don't store full command a string variable to avoid word splitting. Use an array or shell function:
# store command in an array
cmd=(sed -i 's/foo/zoo/' /home/user/bar)
# execute the command
"${cmd[#]}"
Or else use a shell function:
fn() {
sed -i 's/foo/zoo/' /home/user/bar
}
#call it as:
fn
Read this BASH FAQ: I'm trying to put a command in a variable, but the complex cases always fail!
use eval : I am not sure why you want to do it. Note that using eval is a fragil solution and can cause your script to broke.
eval $CMD

How can I easily log some specific command line commands into a file?

I often perform configuration changes using single line commands on Mac OS, Linux or even Windows and I want to easily log them in a file, so I can replay if I have to reconfigure the machine again.
Please not that I want to do these only for some commands, so the shell history is of not use.
Ideally I would like to be able to use some kind of shell extension that logs some of the commands.
As you know if you start your bash command with a space, this command is not logged into the history.
What if I can have another prefix that would do the opposite? Is there something there that can be used for this? A solution for bash would be more than enough and if there is an already existing solution it would much better than me writing a new one.
You could do your logging in PROMPT_COMMAND, extracting the specific commands from shell history and writing them to a file.
Something like:
log () {
last_command="$(history -p \!\!)"
if [[ $last_command == " "* ]] # save commands starting with *two* spaces
then
printf "%s\n" "$last_command" >> ~/special.log
fi
}
PROMPT_COMMAND="log; $PROMPT_COMMAND"
This has problems:
PROMPT_COMMAND is run each time the prompt is printed. Just pressing Enter multiple times could cause a command to be logged multiple times.
Marking with two spaces would, of course, need you to remove ignorespace or ignoreboth from HISTCONTROL so that commands starting spaces are logged at all.
AFAICT, history is updated when the next command is read, so the command is logged after the next command returns to the prompt, since that's when the correct history is available in PROMPT_COMMAND.
All this would be easier in zsh, with a preexec hook:
preexec () {
if [[ $1 == " "* ]]
then
printf "%s\n" "$1" >> ~/special.log
fi
}
The preexec function automatically gets the command as the first argument if history is enabled, saving us a deal of trouble. It is run when the command has been read, but before it begins execution, so the timing is perfect. From the documentation:
preexec
Executed just after a command has been read and is about to be
executed. If the history mechanism is active (regardless of whether
the line was discarded from the history buffer), the string that the
user typed is passed as the first argument, otherwise it is an empty
string. The actual command that will be executed (including expanded
aliases) is passed in two different forms: the second argument is a
single-line, size-limited version of the command (with things like
function bodies elided); the third argument contains the full text
that is being executed.
$ ls
$ echo foo | echo bar
bar
$ cat ~/special.log
ls
echo foo | echo bar
A function in .bashrc can be used like a prefix:
log_this_command () {
echo "$#" >> ~/a_log_file # log the command to file
"$#" # and run the command itself
}
Caveat: this only logs expanded arguments, rather than the raw input.
Source function with the same name function screencapture {echo "used parms: $#"; command screencapture $#}
appending to log file function screencapture {echo "$(date) screencapture " $# >> ~/log.txt; command screencapture $#}
as one runs screencapture command, log entry is created and command executes as uninterfered
you could automate in creating these functions, if the list of them is like .... all of them

How to redirect echoed shell commands as they sre executed

After the question In a shell script: echo shell commands as they are executed I wonder how can I redirect the command executed/echoed to a file (or a variable)?
I tried the usual stdout redirection, like ls $HOME > foo.txt, after setting the bash verbose mode, set -v, but only the output of ls was redirected.
PS: What I want is to have a function (call it "save_succ_cmdline()") that I could put in front of a (complex) command-line (e.g, save_succ_cmdline grep -m1 "model name" /proc/cpuinfo | sed 's/.*://' | cut -d" " -f -3) so that this function will save the given command-line if it succeeds.
Notice that the grep -m1 "model name" ... example above is just to give an example of a command-line with special characters (|,',"). What I expect from such a function "save_succ_cmdline()" is that the actual command (after the function name, grep -m1 "model name"...) is executed and the function verifies the exit code ([$? == 0]) to decide if the command-line can be save or not. If the actual command has succeeded, the function ("save_succ_cmdline") can save the command-line expression (with the pipes and everything else).
My will is to use the bash -o verbose feature to have and (temporarily) save the command-line. But I am not being able to do it.
Thanks in advance.
Your save_succ_cmdline function will only see the grep -m1 "model name" /proc/cpuinfo part of the command line as the shell will see the pipe itself.
That being said if you just want the grep part then this will do what you want.
save_succ_cmdline() {
"$#" && cmd="$#"
}
If you want the whole pipeline then you would need to quote the entire argument to save_succ_cmdline and use eval on "$#" (or similar) and I'm not sure you could make that work for arbitrary quoting.

Using command substitution or similar, but still having script exit (using set -e)

Bash doesn't seem to pass the "exit on error" environment flag into command substitution shells.
I am using a large number of command substitutions (to get around bash's lack of return values), but I'd still like the whole script to go down if something in the subshell fails.
So, for example:
set -e
function do_internet {
curl not.valid.address
}
answer=$(do_internet)
I'd like the script to stop there and then, and not continue.
(I hoped that setting -e would stop from having to put '|| die' on everything.
Am I doing something wrong; and/or is there any way around this?
Here's a little example:
#!/bin/bash
set -e
echo "You should only see this line, and not any other line."
function foo {
false
echo "The above line is false. Figure that one out, Plato."
}
bar=$(foo)
echo $bar
It prints both lines.
(Using GNU bash, version 4.2.25(1)-release (x86_64-pc-linux-gnu))
There is a difference in handling of -e between subshells created with (...), as in Why doesn't bash flag -e exit when a subshell fails?, and subshells created with command substitution $(...), as in the OP.
According to the section COMMAND EXECUTION ENVIRONMENT in the bash manual (and slightly confusingly):
Subshells spawned to execute command substitutions inherit the value of the -e option from the parent shell. When not in posix mode, bash clears the -e option in such subshells.
Regardless of the posix setting, the -e only applies to the subshell created for the purposes of command substitution. So:
$ set -e
# The subshell has -e cleared
$ echo $(false; echo foo)
foo
$ set -o posix
# Now the subshell has -e, so it terminates at `false`
$ echo $(false; echo foo)
$
Nonetheless, -e does apply to the execution of a command which only sets a variable. So
set -e
a=$(false)
will terminate the shell.
However, -e does not apply to individual commands in a function. In the case of
fail() {
false
echo "failed"
}
The return value of fail is 0 (i.e. success) because the echo (which was the last command executed) succeeded. Consequently
a=$(fail) && echo ok
will set a to failed and then print ok

Resources