I have two files.
~/directory1/7120599_S1.txt
hello world!
~/directory2/7120599_S7.txt
bye world!
I'm looking for Perl code that will append the contents of 7120599_S1.txt to the end of 7120599_S7.txt. The following command works in Linux:
$ cat ~/directory1/7120599_S1.txt >> ~/directory2/7120599_S*.txt
New ~/directory2/7120599_S7.txt
hello world!
bye world!
But for some reason this doesn't work in Perl
system "cat ~/directory1/7120599_S1.txt >> ~/directory2/7120599_S*.txt"
Instead it creates a new file in directory2 called 7120599_S*.txt. How do I get Perl to recognize the Linux wildcard character?
If we look at the POSIX shell:
For the other redirection operators, the word that follows the redirection operator shall be subjected to tilde expansion, parameter expansion, command substitution, arithmetic expansion, and quote removal. Pathname expansion shall not be performed on the word by a non-interactive shell; an interactive shell may perform it, but shall do so only when the expansion would result in one word.
Pathname expansion refers to globs like *.txt.
The Bash shell does not follow POSIX precisely and tries to be more convenient and sensible, including applying pathname expansion to redirection targets even in non-interactive mode. Usually, your interactive shell will be Bash.
However, when you run system commands this usually runs a simpler shell that is POSIX-compliant (though that depends entirely on your operating system). Often this shell is installed as /bin/sh.
If you want Bash, you must run Bash explicitly, e.g.
system "bash", "-c", "cat ~/directory1/7120599_S1.txt >> ~/directory2/7120599_S*.txt"
By the way, you can force Bash to follow POSIX if you set the POSIXLY_CORRECT environment variable.
Related
So I was trying to create a script on bash shell, I came to know that the script doesn't run on ksh or dash shells. So my question is how you make a script to run on all 3 (bash, dash & ksh) shells.
In order to write a script that is guaranteed to be portable between the various shells, the script must be POSIX Shell compliant. POSIX is a minimum set of builtins and commands that all conforming shells must support. Ash, Dash, Zsh, Bash, Ksh, etc.. are all shells capable of running scripts that are POSIX compliant.
What shells like Bash do is add nice features which make the shell more capable, like additional parameter expansions for conversion to upper/lower case, substring replacement, etc.. and new builtins like [[ ... ]] that provide regex matching capabilities, etc.. While this makes Bash more capable, it also means scripts written using "Bashisms" are no longer able to run under all other shells. Ash, Dash and other minimal shells have no idea how to handle the features added by Bash, Ksh or Zsh and therefore fail.
To write truly portable scripts, you must limit the content to that provided by the POSIX command language.
You need something file like this:
#!/bin/bash #isn't a simple comment
echo "hello bash"
#!/bin/sh #isn't a simple comment
echo "hello sh"
#!/bin/ksh #isn't a simple comment
echo "hello ksh"
( #!) it's called shebang tells the shell what program to interpret the script
called this file as you better prefert (file.bsk), but don't forget give it execute permission it with :
chmod +x file.bsk
then run ./file.bsk
Some commands or utilities are not available in all shells or they might have different behavior in different shell. If you know which command run on which shell or gives you desired output you can write shell specific commends as below
bash -c 'echo bash'
ksh -c 'echo ksh'
All other commands that are common to all shell can be written in normal way.
I'm using WSL (Ubuntu 18.04) on Windows 10 and bash.
I have a file filename.gpg with the content:
export SOME_ENV_VAR='123'
Now I run the following commands:
$ $(gpg -d filename.gpg)
$ echo $SOME_ENV_VAR
'123' <-- with quotes
However, if I run it directly in the shell:
$ export SOME_ENV_VAR='123'
$ echo $SOME_ENV_VAR
123 < -- without quotes
Why does it behave like this? Why is there a difference between running a command using $() and running it directly?
Aside: I got it working using eval $(gpg -d filename), I have no idea why this works.
Quotes in shell scripts do not behave differently from quotes in shell commands.
With the $(gpg -d filename.gpg) syntax, you are not executing a shell script, but a regular single command.
What your command does
It executes gpg -d filename.gpg
From the result, it takes the first (IFS-separated) word as the command to execute
It takes every other (IFS-separated) words, including words from additional lines, as its parameters
It executs the command
From the following practical examples, you can see how it differs from executing a shell script:
Remove the word export from filename.gpg: the command is then SOME_ENV_VAR='123' which is not understood as a variable assignment (you will get SOME_ENV_VAR='123': command not found).
If you add several lines, they won't be understood as separated command lines, but as parameters to the very first command (export).
If you change export SOME_ENV_VAR='123' to export SOME_ENV_VAR=$PWD, SOME_ENV_VAR will not contain the content of variable PWD, but the string $var
Why is it so?
See how bash performs expansion when analyzing a command.
There are many steps. $(...) is called "command substitution" and is the fourth step. When it is done, none of the previous steps will be performed again. This explains why your command does not work when you remove the export word, and why variables are not substituted in the result.
Moreover "quote Removal" is the last step and the manual reads:
all unquoted occurrences of the characters ‘\’, ‘'’, and ‘"’ that did
not result from one of the above expansions are removed
Since the single quotes resulted from the "command substitution" expansion, they were not removed. That's why the content of SOME_ENV_VAR is '123' and not 123.
Why does eval work?
Because eval triggers another complete parsing of its parameters. The whole set of expansions is run again.
From the manual:
The arguments are concatenated together into a single command, which is then read and executed
Note that this means that you are still running one single command, and not a shell script. If your filename.gpg script has several lines, subsequent lines will be added to the argument list of the first (and only) command.
What should I do then?
Just use source along with process substitution.
source <(gpg -d filename.gpg)
Contrary to eval, source is used to execute a shell script in the current context. Process substitution provides a pseudo-filename that contains the result of the substitution (i.e. the output of gpg).
I want to send little script to remote machine by ssh
the script is
#!/bin/bash
sleep 1
reboot
but I get event not found - because the "!"
ssh 183.34.4.9 "echo -e '#!/bin/bash\nsleep 1\reboot>'/tmp/file"
-bash: !/bin/bash\nsleep: event not found
how to ignore the "!" char so script will so send successfully by ssh?
remark I cant use "\" before the "!" because I get
more /tmp/file
#\!/bin/bash
sleep 1
Use set +H before your command to disable ! style history substitution:
set +H
ssh 183.34.4.9 "echo -e '#!/bin/bash\nsleep 1\reboot>'/tmp/file"
# enable hostory expnsion again
set -H
I think your command line is not well formated. You can send this:
ssh 183.34.4.9 'echo -e "#!/bin/bash\nsleep 1\nreboot">/tmp/file'
When I say "not well formated" I mean you put ">" inside the "echo" and you forgot to add "n" before "reboot", and you put "\reboot", wich will be interpreted as "CR" (carriage return) followed by "eboot" command (which I don't think that exists).
But what did the trick here is to invert the comas changing (') with (") and viceversa.
Bash is running interactively (which means that you are feeding commands to it from the standard input and not exec(2)ing a command from a shell script) so you don't need to include the line #!/bin/bash in that case (even more, bash should just ignore it, but not the included bang, as it is part of the active history mechanism)
But why? the first two characters in an executable file (any file capable of being exec(2)ed from secondary storage, not your case) have a special meaning (for the kernel and for the shell): they are the magic number that identifies the kind of executable file the kernel is loading. This allows the kernel to select the proper executable loading routines depending on the binary executable format (and what allows you for example to execute BSD programs in linux kernels, and viceversa)
A special value for this magic numbers is composed by the two characters # and ! (in that order) that forces the kernel to read the complete first line of that file and load the executable file specified in that line instead, allowing you to execute shell scripts for different interpreters directly from the command line. And it is done on purpose, as the # character is commonly in shell script parlance a comment character. This only happens when the shell that is interpreting the commands is not an interactive shell. When the shell loads a script with those characters, it normally reads the first line also to check if it has the #! mark and load the proper interpreter, by replicating the kernel function that does this. Despite of being a comment for the shell, it does this to allow to treat as executables files that are not stored on secondary storage (the only ones the exec(2) system call can deal with), but coming from stdin (as happens to yours).
As your shell is running interactively and you do want to execute its commands without a shell change, you don't need that line and can completely eliminate it without having to disable the bang character.
Sorry, but the solution given about executing the shell with -H option will probably not be viable, as the shell executing the commands is the login shell in the target machine, so you cannot provide specific parameters to it (parameters are selected by the login(8) program and normally don't include arbitrary parameters like -H).
The best solution is to fully eliminate the #!/bin/bash line, as you are not going to exec(2) that program in the target. In case you want to select the shell from the input line (case the user has a different shell installed as login shell), it is better to invoke the wanted shell in the command line and pass it (through stdin, or making it read the shell script as a file) the shell commands you wan to execute (but again, without the #! line).
NOTE
Its important to ensure you'll execute the whole thing, so it's best to pass all the script contents in the destination target, and once assured you have passed the whole thing to execute it as a whole. Then your #! first line will be properly processed, as the executable will be run by means of an exec(2) made from the kernel.
Example:
DIRECTORY=/bla/bla
FILE=/path/to/file
OUTPUT=/path/to/output
# this is the command we want to pass through the line
cat <<EOF | ssh user#target "cat >>/tmp/shell.sh"
cd $DIRECTORY
foo $FILE >$OUTPUT
exit 0
EOF
# we have copied the script file in a remote /tmp/shell.sh
# and we are sure it has passed correctly, so it's ready
# for local execution there.
# now, execute it.
# the remote shell won't be interactive, and you'll ensure that it is /bin/bash
ssh user#target "/bin/bash /tmp/shell.sh" >remote_shell.out
A more sophisticate system is one that allows to to sign the shell script before sending, and verify the script signature before executing it, so you are protected against possible trojan horse attacks. But this is out of scope on this explanation.
Another alternative is to use the batch(2) command remotely and pass it all the commands you want executed. you'll get a sessionless executing environment, more suitable to the task you are demanding (despite the fact that you'll get the script output by email to the target user running the script)
Interactively, beware that ! triggers history expansion inside double quotes
from here: https://riptutorial.com/bash/example/2465/quoting-literal-text
my recommended solution is to use single quotes to define the string (and either escape single quotes \' or use double quotes " within the string):
ssh 183.34.4.9 'echo -e "#!/bin/bash\nsleep 1\reboot>"/tmp/file'
I am using bash and this works on Linux:
read -r -d '' VAR<<-EOF
Hello\nWorld
EOF
echo $VAR > trail
i.e the contents of the file on Linux is
Hello\nWorld
When i run on Solaris
trial file has
Hello
World
The newline(\n) is being replaced with a newline. How can i avoid it?
Is it a problem with heredoc or the echo command?
[UPDATE]
Based on the explanation provided here:
echo -E $VAR > trail
worked fine on Solaris.
The problem is with echo. Behavior is defined in POSIX, where interpretting \n is part of XSI but not basic POSIX itself.
You can avoid this on all platforms using printf (which is good practice anyways):
printf "%s\n" "$VAR"
This is not a problem for bash by the way. If you had used #!/usr/bin/env bash as the shebang (and also not run the script with sh script), behavior would have been consistent.
If you use #!/bin/sh, you'll get whichever shell the system uses as a default, with varying behaviors like this.
To complement #that other guy's helpful answer:
Even when it is bash executing your script, there are several ways in which the observed behavior - echo by default interpreting escape sequences such as \n - can come about:
shopt -s xpg_echo could be in effect, which makes the echo builtin interpret \ escape sequences by default.
enable -n echo could be in effect, which disables the echo builtin and runs the external executable by default - and that executable's behavior is platform-dependent.
These options are normally NOT inherited when you run a script, but there are still ways in which they could take effect:
If your interactive initialization files (e.g., ~/.bashrc) contain commands such as the above and you source (.) your script from an interactive shell.
When not sourcing your script: If your environment contains a BASH_ENV variable that points to a script, that script is sourced before your script runs; thus, if that script contains commands such as the above, they will affect your script.
I have just started using Linux and I am curious how shell built-in commands such as cd are defined.
Also, I'd appreciate if someone could explain how they are implemented and executed.
If you want to see how bash builtins are defined then you just need to look at Section 4 of The Bash Man Page.
If, however, you want to know how bash bultins are implemented, you'll need to look at the Bash source code because these commands are compiled into the bash executable.
One fast and easy way to see whether or not a command is a bash builtin is to use the help command. Example, help cd will show you how the bash builtin of 'cd' is defined. Similarly for help echo.
The actual set of built-ins varies from shell to shell. There are:
Special built-in utilities, which must be built-in, because they have some special properties
Regular built-in utilities, which are almost always built-in, because of the performance or other considerations
Any standard utility can be also built-in if a shell implementer wishes.
You can find out whether the utility is built in using the type command, which is supported by most shells (although its output is not standardized). An example from dash:
$ type ls
ls is /bin/ls
$ type cd
cd is a shell builtin
$ type exit
exit is a special shell builtin
Re cd utility, theoretically there's nothing preventing a shell implementer to implement it as external command. cd cannot change the shell's current directory directly, but, for instance, cd could communicate new directory to the shell process via a socket. But nobody does so because there's no point. Except very old shells (where there was not a notion of built-ins), where cd used some dirty system hack to do its job.
How is cd implemented inside the shell? The basic algorithm is described here. It can also do some work to support shell's extra features.
Manjari,
Check the source code of bash shell from ftp://ftp.gnu.org/gnu/bash/bash-2.05b.tar.gz
You will find that the definition of shell built-in commands in not in a separate binary executable but its within the shell binary itself (the name shell built-in clearly suggests this).
Every Unix shell has at least some builtin commands. These builtin commands are part of the shell, and are implemented as part of the shell's source code. The shell recognizes that the command that it was asked to execute was one of its builtins, and it performs that action on its own, without calling out to a separate executable. Different shells have different builtins, though there will be a whole lot of overlap in the basic set.
Sometimes, builtins are builtin for performance reasons. In this case, there's often also a version of that command in $PATH (possibly with a different feature set, different set of recognized command line arguments, etc), but the shell decided to implement the command as a builtin as well so that it could save the work of spawning off a short-lived process to do some work that it could do itself. That's the case for bash and printf, for example:
$ type printf
printf is a shell builtin
$ which printf
/usr/bin/printf
$ printf
printf: usage: printf [-v var] format [arguments]
$ /usr/bin/printf
/usr/bin/printf: missing operand
Try `/usr/bin/printf --help' for more information.
Note that in the above example, printf is both a shell builtin (implemented as part of bash itself), as well as an external command (located at /usr/bin/printf). Note that they behave differently as well - when called with no arguments, the builtin version and the command version print different error messages. Note also the -v var option (store the results of this printf into a shell variable named var) can only be done as part of the shell - subprocesses like /usr/bin/printf have no access to the variables of the shell that executed them.
And that brings us to the 2nd part of the story: some commands are builtin because they need to be. Some commands, like chmod, are thin wrappers around system calls. When you run /bin/chmod 777 foo, the shell forks, execs /bin/chmod (passing "777" and "foo") as arguments, and the new chmod process runs the C code chmod("foo", 777); and then returns control to the shell. This wouldn't work for the cd command, though. Even though cd looks like the same case as chmod, it has to behave differently: if the shell spawned another process to execute the chdir system call, it would change the directory only for that newly spawned process, not the shell. Then, when the process returned, the shell would be left sitting in the same directory as it had been in all along - therefore cd needs to be implemented as a shell builtin.
A Shell builtin -- http://linux.about.com/library/cmd/blcmdl1_builtin.htm
for eg. -
which cd
/usr/bin/which: no cd in (/usr/bin:/usr/local/bin......
Not a shell builtin but a binary.
which ls
/bin/ls
http://ss64.com/bash/ this will help you.
and here is shell scripting guide
http://www.freeos.com/guides/lsst/