-3

I am trying to store a command in a variable, so that it can be run on a remote server later. The asterisk is being replaced with the folder names and is being stored in the variable. I need the command to be as it is with the asterisk fir later usage.

Script:

#!/bin/bash
cmd="ls -lrt /client/*/ver* /client/*/*/ver*  | grep 299 | tail -1"
echo $cmd

Output:

./script.sh 
ls -lrt /client/folder299/version_1 /client/ifolder299/ifolder/version_a /client/ifolder300/ifolder1/version_b /client/ifolder301/ifolder2/version_c /client/ifolder302/ifolder3/version_d | grep 299 | tail -1

I tried searching around with no luck. Could someone help me with a way to store the command as it is?

Raj
  • 203
  • check this post https://unix.stackexchange.com/questions/87405/how-can-i-execute-local-script-on-remote-machine-and-include-arguments – Kamaraj Jul 11 '18 at 06:34
  • That's most probably because you're running a different shell on your Unix machine. Try running the same shell and see what happens. If you don't have the same shell read the manual page for the one you're using and adapt the script. – YoMismo Jul 11 '18 at 06:35
  • you should escape the special characters in your command. Also please refer the link i posted above. – Kamaraj Jul 11 '18 at 06:36
  • 1
    Why are you trying to store a command pipeline in a variable? That's almost certainly the wrong thing to do. Also, your script lacks a #! line. See https://unix.stackexchange.com/questions/444946 – Kusalananda Jul 11 '18 at 06:47
  • @Kamaraj Sorry, if I confused you. My problem here is not with the remote command execution. I want a way to store the command as a string in a variable for using later. I updated my question to be clear. Thanks in advance. – Raj Jul 11 '18 at 06:50
  • 1
    I can not reproduce this issue with bash. The string is stored and outputted as is. The only thing is that you're echoing the expanded variable unquoted, which would invoke filename globbing. Using printf '%s\n' "$cmd" would prevent that (or even echo "$cmd"). But the command would never be evaluated (executed). – Kusalananda Jul 11 '18 at 06:56
  • 1
    Could you please clarify what you mean by "the command is evaluated"? Does it run the pipeline and store the result of the final tail -1 in cmd? – Kusalananda Jul 11 '18 at 07:12
  • Probably not a kernel (Linux) thing, probably shell. Which Unix, which Linux? (This is why calling Gnu+Linux, just linux is a bad idea). – ctrl-alt-delor Jul 11 '18 at 08:12
  • have you considered defining a shell function? – ctrl-alt-delor Jul 11 '18 at 08:13
  • @Kusalananda The Unix server here is SunOS(solaris). As per this link, bash is the default shell (https://docs.oracle.com/cd/E19683-01/806-7612/startup-2200/index.html). I have tried by adding the #!/bin/bash explicitly and still it doesn't work. I tried using printf, which also does the same thing. I understand the initial assignment is where the command in quotes is being executed and stored in variable. – Raj Jul 11 '18 at 08:42
  • 1
    @Raj The bash shell just does not do that. – Kusalananda Jul 11 '18 at 08:50
  • This does not do as you would expect on Debian (jessie) Gnu/Linux using bash (4.3.30), either. it passes |, grep, etc to ls. Please re-test, is this what you tried last time. (can not reproduce) – ctrl-alt-delor Jul 11 '18 at 12:39
  • @Kusalananda Edited the question to be more clear. – Raj Jul 13 '18 at 12:35
  • @Raj Yeah, that's what I thought. The command is not executed, but the globbing patterns are expanded due to the cmd variable being unquoted with echo. See my updated answer. – Kusalananda Jul 13 '18 at 12:40
  • @Kusalananda Thanks, your answer works. I should have been clear with my question the first time I asked. I was trying to avoid showing the actual folders. – Raj Jul 13 '18 at 12:47

4 Answers4

1

The command is not evaluated and the string is stored as is in the variable. It's the globbing patterns that are expanded when you output the unquoted variable.


cmd="ls -lrt /client/*/ver* /client/*/*/ver*  | grep 299 | tail -1"

This is safe and would set cmd to the literal string ls -lrt /client/*/ver* /client/*/*/ver* | grep 299 | tail -1. The difference between using double quotes and single quotes around the string doesn't matter in this case (but I would have used single quotes as this is a static string). It doesn't matter in the sense that there is no expansion of anything for the shell to do in the string. Had there been variables or command substitutions in the string, the shell would have expanded those, but there are none.

When you output the value of the variable using echo $cmd, the globbing patterns would be expanded (this is what happens), but the command would still not be run.

To stop the globbing patterns from being expanded, double quote the variable expansion as "$cmd". Personally, I would use

printf '%s\n' "$cmd"

to print its value. See "Why is printf better than echo?" for why.

Related:


Note also that since you're parsing the output of ls -l (which in itself is not safe), and then grepping for a number, you would pick that number up from anywhere in the ls -l output, for example in the size of a file.

I would advise that you rethink what the bigger picture issue is that you're trying to solve, and then that you ask a brand new question about that instead.

To find the most recently modified regular file out of a list of pathnames, you may do

unset newest
for pathname in /client/*/ver* /client/*/*/ver*; do
    if [ -f "$pathname" ] && [ "$pathname" -nt "$newest" ]; then
        newest=$pathname
    fi
done

This is assuming that the filename globbing patterns that you've mentioned expands to the names that you'd like to check. To additionally restrict this to only allow filenames containing the string 299:

unset newest
for pathname in /client/*/ver* /client/*/*/ver*; do
    if [[ "${pathname##*/}" == *299* ]] &&
        [ -f "$pathname" ] && [ "$pathname" -nt "$newest" ]; then
        newest=$pathname
    fi
done

If the globbing patterns expand to directories that you need to look into recursively, then using bash:

shopt -s globstar
unset newest
for pathname in /client/*/ver*/** /client/*/*/ver*/**; do
    if [[ "${pathname##*/}" == *299* ]] &&
        [ -f "$pathname" ] && [ "$pathname" -nt "$newest" ]; then
        newest=$pathname
    fi
done

Example of running this over SSH:

ssh user@host bash -s -O globstar <<'END_SCRIPT'
for pathname in /client/*/ver*/** /client/*/*/ver*/**; do
    if [[ "${pathname##*/}" == *299* ]] &&
        [ -f "$pathname" ] && [ "$pathname" -nt "$newest" ]; then
        newest=$pathname
    fi
done
printf 'Newest file: %s\n' "$newest"
END_SCRIPT
Kusalananda
  • 333,661
  • Thanks for the script. I am trying to use the last example provided above, but it doesn't print the file. Tried searching around and found this https://stackoverflow.com/a/26766782/9316558. This script works in printing the latest file, but I have trouble modifying it to match a pattern (299). – Raj Jul 19 '18 at 06:57
  • @Raj Does the globbing patterns /client/*/ver* and /client/*/*/ver* resolve to directory names or to filenames that may contain 299? – Kusalananda Jul 19 '18 at 07:01
  • 299 is a string in folder name. Eg : /client/folder299/version_1 /client/ifolder299/ifolder/version_a . Basically I am trying get the latest version of the server ID 299 (In this case either version_1 or version_a). Please let me know if something is not clear. – Raj Jul 19 '18 at 08:07
  • @Raj Ah, this was unclear from the question. It is also sufficiently different from the actual question that I'm not going to answer it here. Instead, I suggest that you ask a new question about this specifically, and there mention that you'd like to find a particular file in a particular path, and what this file is for etc. – Kusalananda Jul 19 '18 at 08:12
  • I agree with you. https://unix.stackexchange.com/q/457157/250029 opened for it. – Raj Jul 19 '18 at 08:50
0

May be the option f :

$ touch /tmp/filetmp
$ c="ls /tmp/*"

$ set -f
$ echo $c
ls /tmp/*

$ set  +f
$ echo $c
ls /tmp/filetmp

$ rm /tmp/filetmp
$ unset c

Under linux, usually by default the option is +f

alux
  • 36
0

Sorry for the above confusion.

The issue is resolved with providing the command directly in the following way.

ssh username@host "ls -lrt /client/*/ver* /client/*/*/ver* | grep $4 | tail -1" 
Raj
  • 203
  • If you had said something about how you were going to use the string, we would have found a solution for you much quicker. Note that this command would still give you false positives if the grep matches anything that isn't a filename in the ls output. Also, since $4 is unquoted, filename matching and word splitting will occur on its value. This is not the command you would want to use in a production system. – Kusalananda Jul 18 '18 at 07:17
  • @Kusalananda Thanks for the insight. As you understood, I want to get the latest file from the remote system for a provided server number. I am using the above command, where the $4 represents the server number. Are you suggesting that quoting the $4 would be better or is there a better approach towards it. I am an amateur in shell, trying to automate what we do manually. – Raj Jul 18 '18 at 08:22
  • I have doodled a bit at the end of my answer. – Kusalananda Jul 18 '18 at 09:36
-2

Use single quote while storing the commands into variable. Use double quotes while using in echo command.

$ cmd='ls -lrt /var/* /tmp/* /home/* | grep test | tail -1'
$ echo "$cmd"
ls -lrt /var/* /tmp/* /home/* | grep test | tail -1

Difference between single and double quotes

cezar
  • 113
Kamaraj
  • 4,365
  • The double quotes used when assigning to cmd is not an issue here as the string doesn't contain anything that the shell would expand. Double quoting the expansion when printing the value is important though. It still doesn't explain why, on the user's system, it actually runs the command (which I haven't been able to replicate). – Kusalananda Jul 11 '18 at 07:10
  • @Kamaraj , Thanks for the response. The single quote also doesn't seem to work. The expression in the quotes is being evaluated(I mean, the output we get when this command is executed on the command line) and stored in the variable. – Raj Jul 11 '18 at 08:58