How is $() different from redirection? - linux

Im learning the commandline from the book The Linux command line and I have a doubt.
Should not
ls -l $(which cp)
and which cp | ls -l have the same output?
Because I'm taking the output of which cp and passing it to ls -l
But that does not work as expected. which cp | ls -l instead displays the contents of pwd

ls doesn't care what's in the standard input.
echo anything | ls -l
^^^
Since you haven't provided a directory to list, it will list the pwd.

In the first case ls is receiving the result as an argument, in the second it is receiving it in the input stream (stdin), wich is ignored in this case.
You can convert from the input stream to arguments using xargs :
which cp | xargs ls -l

Related

ssh tail with nested ls and head cannot access

am trying to execute the following command:
$ ssh root#10.10.10.50 "tail -F -n 1 $(ls -t /var/log/alert_ARCDB.log | head -n1 )"
ls: cannot access /var/log/alert_ARCDB.log: No such file or directory
tail: cannot follow `-' by name
notice the error returned, when i login to ssh separately and then execute
tail -F -n 1 $(ls -t /var/log/alert_ARCDB.log | head -n1 )"
see the below:
# ls -t /var/log/alert_ARCDB.log | head -n1
/var/log/alert_ARCDB.log
why is that happening and how to fix it. am trying to do this in one line as i don't want to create a script file.
Thanks a lot
Shell parameter expansion happens before command execution.
Here's a simple example. If I type...
ls "$HOME"
...the shell replaces $HOME with the path to my home directory first, then runs something like ls /home/larsks. The ls command has no idea that the command line originally had $HOME.
If we look at your command...
$ ssh root#10.10.10.50 "tail -F -n 1 $(ls -t /var/log/alert_ARCDB.log | head -n1 )"
...we see that you're in exactly the same situation. The $(ls -t ...) expression is expanded before ssh is executed. In other words, that command is running your local system.
You can inhibit the shell expansion on your local system by using single quotes. For example, running:
echo '$HOME'
Will produce:
$HOME
So you can run:
ssh root#10.10.10.50 'tail -F -n 1 $(ls -t /var/log/alert_ARCDB.log | head -n1 )'
But there's another problem here. If /var/log/alert_ARCDB.log is a file, your command makes no sense: calling ls -t on a single file gets you nothing.
If alert-ARCDB.log is a directory, you have a different problem. The result of ls /some/directory is a list of filenames without any directory prefix. If I run something like:
ls -t /tmp
I will get output like
file1
file2
If I do this:
tail $(ls -t /tmp | head -1)
I end up with a command that looks like:
tail file1
And that will fail, because there is no file1 in my current directory.
One approach would be to pipe the commands you want to perform to ssh. One simple way to achieve that is to first create a function that will echo the commands you want executed :
remote_commands()
{
echo 'cd /var/log/alert_ARCDB.log'
echo 'tail -F -n 1 "$(ls -t | head -n1 )"'
}
The cd will allow you to use the relative path listed by ls. The single quotes make sure that everything will be sent as-is to the remote shell, with no local expansion occurring.
Then you can do
ssh root#10.10.10.50 bash < <(remote_commands)
This assumes alert_ARCDB.log is a directory (or else I am not sure why you would want to add head -n1 after that).

How to reference the output of the previous command twice in Linux command?

For instance, if I'd like to reference the output of the previous command once, I can use the command below:
ls *.txt | xargs -I % ls -l %
But how to reference the output twice? Like how can I implement something like:
ls *.txt | xargs -I % 'some command' % > %
PS: I know how to do it in shell script, but I just want a simpler way to do it.
You can pass this argument to bash -c:
ls *.txt | xargs -I % bash -c 'ls -l "$1" > "out.$1"' - %
You can lookup up 'tpipe' on SO; it will also lead you to 'pee' (which is not a good search term elsewhere on the internet). Basically, they're variants of the tee command which write to multiple processes instead of writing to files like the tee command does.
However, with Bash, you can use Process Substitution:
ls *.txt | tee >(cmd1) >(cmd2)
This will write the input to tee to each of the commands cmd1 and cmd2.
You can arrange to lose standard output in at least two different ways:
ls *.txt | tee >(cmd1) >(cmd2) >/dev/null
ls *.txt | tee >(cmd1) | cmd2

Why "which cp | ls -l " is not treate as "ls -l $(which cp)"?

According to pipe methodology in Linux, the output of the first command should be treated as input for the second command. So when I am doing which cp | ls -l, it should be treated as ls -l $(which cp)
But the output is showing something else.
Why so ?
ls does not take input from stdin. You can work around this if you need to by using xargs:
which cp | xargs ls -l
This will invoke ls -l with the (possibly multiple, if which were to return more than one) filenames as command line arguments, with no standard input.

what is the use of running shell script like "$ ls -la | script.sh"?

To get total number of lines read, why we are using ls -la | script.sh ?
Why we cant execute normal way like script.sh ?
Note that script.sh is the shell script program.
Breaking it down bit by bit:
ls -la
List all files (including dotfiles) in long format.
|
Sends the output of the command on the left to the command on the right
script.sh
Executes the script.
So the output of ls -la will be sent via stdin to script.sh.
To get total number of lines, that is files in your case, simply
ls | wc -l

how to pipe parsed command into shell interpretor?

I have a 'fancy_awk_script' which parse file names into shell command
myself#supercomputer /home/myself $ ls -1 *.MYLOG.csv | fancy_awk_script
cp 20120607.MYLOG.csv 20120607.MYLOG.csv2
mv 20120606.MYLOG.csv 20120607.MYLOG2.csv
cp 20120605.MYLOG.csv 20120606.MYLOG.csv
...
i want pipe above result directly to a shell interpretor, what should I do? something like this?
myself#supercomputer /home/myself $ ls -1 *.MYLOG.csv | fancy_awk_script | xargs -E
can anyone help?
thanks!
piping directly into sh works if you don't expect any user input (say it was cp -i).
ls -1 *.MYLOG.csv | fancy_awk_script |xargs -i bash -c {}

Resources