0

I am trying to write a bash function that takes command as the first variable and output file as the second variable:

my_func() {
    $1 ${SOME_OTHER_PARAMS} |
    tee $2
}

when I run my_func "python print_hello_to_a_file ~/out.txt" "second_file.txt", it seems to output "hello" to "~/out.txt", not "out.txt" in my home directory, meaning that "~" was not expanded correctly.

I am wondering if it is possible to correctly expand "~" inside this function?

tripleee
  • 175,061
  • 34
  • 275
  • 318
username123
  • 913
  • 1
  • 11
  • 29
  • 1
    Sorry, but overall this is a confusing approuch, with _many_ pitfalls. What exactly are you trying to do? Start by checking your scripts with shellcheck. `to correctly expand "~"` What does it mean "correctly"? You can _replace_ `~` by the string representing your home directory path, but "correctly" you would have to write whole shell parser. `it seems to output "hello" to "~/out.txt"` That is odd, it should output the string `hello > ~/out.txt` into `second_file.txt`. Please check again, `>` is not a redirection when it's a result of a unquoted expansion, it's just a character `>`. – KamilCuk Jun 27 '22 at 17:00
  • @KamilCuk Basically I want to make sure whatever gets passed to $1 gets correctly expanded... – username123 Jun 27 '22 at 17:03
  • Can you use zsh instead? – Shawn Jun 27 '22 at 17:03
  • > "You can replace ~ by the string representing your home directory path" Right I can manually replace the string, but I am wondering if it is possible to do that automatically, like it is usually done in bash – username123 Jun 27 '22 at 17:04
  • @Shawn No unfortunately zsh is not installed in server – username123 Jun 27 '22 at 17:05
  • 1
    `it seems to output "hello" to "~/out.txt"` I cannot reproduce [repl link](https://replit.com/@kamilcukrowski/WoefulCoordinatedRedundancy#main.sh).It outputs the string `hello > ~/out.txt` into the `second_file.txt` file. `>` is not a redirection as part of a string. `do that automatically` Yes, you can replace, with `sed` the string `~` by the home directory. `like it is usually done in bash` You may want to use `eval`, but `eval` is evil. https://stackoverflow.com/questions/17529220/why-should-eval-be-avoided-in-bash-and-what-should-i-use-instead . – KamilCuk Jun 27 '22 at 17:07
  • 1
    There are situations where you can't avoid `eval`, and this seems like one of them if you really wish to push ahead. As others have already remarked, though, that's probably not a good idea. – tripleee Jun 27 '22 at 17:09

1 Answers1

0

Possible? Yes, but probably not a good idea.

The basic problem: When parsing the command line, Tilde Expansion happens before Parameter Expansion. This means you can't put a tilde inside a variable and have it be replaced by a path to your home directory in the simplest case.

Minimal demo:

[user@host]$ myvar="~"
[user@host]$ echo $myvar
~
[user@host]$ echo ~
/home/user

One possible solution is to use eval to force a second round of parsing before executing the command.

[user@host]$ eval echo $myvar
/home/user

But eval is VERY DANGEROUS and you should not use it without exhausting all other possibilities. Forcing a second parsing of the command line can result in unexpected, confusing, and potentially even unsafe results if you are not extremely familiar with the parsing rules and take sufficient steps to sanitize your inputs before running them through eval.

The more standard solution is to build up your command inside a bash array.

my_func() {
  tee_output="${1}"
  shift
  # expand the inputs all together
  # SOME_OTHER_PARAMS should be an array as well
  "${@}" "${SOME_OTHER_PARAMS[@]}" | tee "${tee_output}"
}

# build the command up as an array with each word as its own element
# Tilde expansion will occur here and the result will be stored for later
my_command=( "python" "print_hello_to_a_file" ~/"out.txt" )

# expand the array and pass to my_func
# Important that the tee_location goes first here, as we 
# shift it off to capture the remaining arguments as a complete command
my_func "${tee_loc}" "${my_command[@]}"

But my_func still only supports simple commands with this approach - no loops or if/case statements, no file redirections, etc. This might be okay if your goal is just to decorate a variety of commands with extra parameters and tee their output somewhere.

tjm3772
  • 2,346
  • 2
  • 10