Server Bug Fix: Why can’t I enable the ‘new’ button of action?

Original Source Link

I want to import the action template of action editor, and use template ID to display it.
But the ‘new’ button is disabled, how could I active it?

here is my code currently:

import bpy

def drawRenderEngine(self, context):
        layout = self.layout
        ob = context.object

        window = context.window
        screen = context.screen
        scene = window.scene

        if ob is not None:
            bpy.context.object.animation_data_create()
            layout.template_ID(context.object.animation_data, "action", new='action.new')




bpy.types.TOPBAR_HT_upper_bar.draw_right = drawRenderEngine

enter image description here

The operator does not poll.

The issue arises since the operator bpy.ops.action.new does not poll in the toolbar header.

Similarly in python console

>>> bpy.ops.action.new.poll()
False

Closer inspection of the action editor UI code, will see that the template object is the dope sheet space st = context.space_data not the animation data of the context object. My guess is the operator uses space to poll.

Code wise will confess to never using this operator; to add a new action would instead

action = bpy.data.actions.new(name)

and assign it to the animation data of the object I’m animating.

Enough waffle. Recommend here creating an operator bpy.ops.foo.bar that makes the new action and assigns it where you wish, it appears from Q this is on the active object Call the following from your operators execute method.

def main(context):
    ob = context.object
    name = f"{ob.name}Action"
    action = bpy.data.actions.new(name)
    # choose whether to set as active action or whatever
    ob.animation_data_create()
    ob.animation_data.action = action

Have it poll if there is a context object

Then use it instead for new

layout.template_ID(ob.animation_data, "action", new="foo.bar")

Side Note.

The draw method is only meant for drawing to the UI. If you put an __init__ in a panel class will see they are initialized constantly.

Trying to alter objects in any way during a draw will result in an error. This applies to

bpy.context.object.animation_data_create()

in question code.

Tagged : / / / /

Server Bug Fix: Read the contents of a Text Datablock

Original Source Link

Writing into a text datablock can be done with the script

text = bpy.data.texts[name]
text.write(something)

However, when I used the script text.read, an error appeared.

Question: How do I read the content in a text datablock with a script?

TextBlock.as_string()

Will give the complete contents of the text block.

Test Text block “FooBarBar”

Foo
Bar
Bar

Once again off to the python console to test.

>>> D.texts['FooBarBar'].as_string()
'FoonBarnBar'

>>> print(D.texts['FooBarBar'].as_string())
Foo
Bar
Bar

>>> 

same result as joining lines with

>>> 'n'.join(l.body for l in D.texts['FooBarBar'].lines)
'FoonBarnBar'

On the error…

>>> D.texts['FooBarBar'].read
Traceback (most recent call last):
  File "<blender_console>", line 1, in <module>
AttributeError: 'Text' object has no attribute 'read'

The error appears in RED that the text block has no read method. This is confirmed by consulting the documentation

My 2c worth of advice, try autocomplete in the console “@MartinZ style“, consult the docs, and then….

I have never needed to work with text blocks so I have no idea yet. Let’s find out.

If you press Tab after writing something in the Python Console, you can see all the available options from auto complete like this:

enter image description here

We can see that it is possible to access lines text.lines so if we want to read the 4th line for example we carry on with auto complete:

enter image description here

and see that it has body, that’s probably the thing we need here then:

enter image description here

It’s a string. That’s it.

Tagged : /

Code Bug Fix: Connect via ssh with a single .bat script to multiple Adresses

Original Source Link

I have a environment of 27 Mikrotik Routers and I want to add a user on each one with same credentials.

Normally I had to connect on every Router and click through the GUI to add the user, but now I found a way to use SSH connection via cmd.

I wrote this – which connects on a single Router and performs the Add-User process

ssh [email protected] -password "Passw0rd!" "user add name=customer-support password=#F0ry0u! group=full"

But now I want to make a script which maybe reads in a csv file with the ip adresses of all the routers I want to perform the change on and connect on each Router to execute the command.
Is this possible?

Since SSH is available for MikroTik router, you can indeed read their addresses from a file.

For example, use bash (which you can execute even on Windows through WSL/WSL2 or Git for Windows which includes a MinGW bash)

See for example “Bash Read Comma Separated CSV File on Linux / Unix“, here adapted to your case

#!/bin/bash
# Purpose: Read Comma Separated CSV File
# Author: Vivek Gite under GPL v2.0+
# ------------------------------------------
INPUT=data.cvs
OLDIFS=$IFS
IFS=','
[ ! -f $INPUT ] && { echo "$INPUT file not found"; exit 99; }
while read maddress
do
    echo "Address : $maddress"
    # do your SSH call here
done < $INPUT
IFS=$OLDIFS

But if you really need a bat script, see for instance “Help in writing a batch script to parse CSV file and output a text file“.

Tagged : / /

Server Bug Fix: Can’t get a cronjob to run a script that installs a service which requires root

Original Source Link

I need to run, on Ubuntu 18.04, via cron a script, myscript.sh that contains, among other things, installation instruction for another script, pluckeye-linux-0.99.40.installer and I’m running into a tedious error:

The script in question is the installer for Pluckeye a kind of parental control app. This needs to be run as root. Inside of myscript.sh I have placed

cd "/path/to/pluckeye/"
./pluckeye-linux-0.99.40.installer
  • When I run sudo crontab -e and place there

    * 20 * * * "/path/to/myscript.sh"

    then Pluckeye’s installer returns a cryptic error (I’m redirecting the outputs of what cron execute to a file, so that I can see what went wrong):

    0b8e:24: ca151e1e WARNING 32512
    0b8e:33: ca151e1e WARNING 0x30b8e019
    0b8e:42: ca151e1e WARNING 0x30b8e019
    0b8e:78: ca151e1e WARNING 0x30b8e019
    0c2c:14: ca151e1e WARNING 0x30b8e019
    0b4c:20: ca151e1e WARNING 0x30b8e019
    0c70:48: ca151e1e WARNING 0x30b8e019
    FAILED TO INSTALL: 6400

  • But when I run the installer normally in my shell with sudo (otherwise it will immediately return and error

    I need to be invoked by root
    FAILED TO INSTALL: 11520

    ) it works without any problems.


I assume this comes from the fact that cron uses its own, minimal environment. I have tried various things I found on the internet, like using instead of the above

* 20 * * * . $HOME/.profile; "/path/to/myscript.sh"

hoping to get the installer to work now, but that changes nothing. I would not like to place sudo inside the script, meaning

cd "/path/to/pluckeye/"
sudo ./pluckeye-linux-0.99.40.installer

as I have read that that introduces a security risk.

What do I need to do to get Pluckeye to install itself via cron?

maybe you would do this:

  1. using crontab -e
* 20 * * * sh /path/to/myscript.sh
  1. using /etc/crontab
* 20 * * * root cd /path/to/pluckeye/ && sh ./pluckeye-linux-0.99.40.installer

Tagged : / / /

Server Bug Fix: Queue VMware Virtual Machine vMotion using powercli

Original Source Link

I wrote this quick Powershell script where it queues vMotion of VMs and runs only 4 vMotion tasks at a time. I thought this might help someone, so you are free to modify the code as per your like.

We were moving VMs to 2 different datastore for a DR exercise overnight.

I have placed comments in the script, but if you have any questions, please comment below. I am sure this can be done in a much better way, but this was a quick thing as I had to leave it running, so the VMs are migrated overnight.

The main command that helped me was get-task.

I am not sure why I didn’t have to specify Host for vMotion but VMs were migrated to correct hosts, Maybe DRS took care of that

Start-Transcript

#import the servernames from csv file
Import-Csv 'C:UsersAdministratorDesktopRestoreDetails Queue.csv' | foreach {

    #check if vm already on required datastore, change the datastore name as per your environment
    if ((get-vm $_.host |Get-Datastore).name -ne "DataStore1" -or (get-vm $_.host |Get-Datastore).name -ne "DataStore2") {

        Write-Host "`nStaring vMotion session for "$_.host

        #Get current running tasks for Relocatin VM and check if the count is lower than 4
        #I am checking for currently running tasks and with Perecentage over 0%, sometimes there are tasks that are just waiting
        if ((Get-Task | ?{$_.name -eq "RelocateVM_Task" -and $_.state -eq "Running" -and $_.PercentComplete -ne "0"}).count -lt "4") {


            Write-Host "`nLess than 4 vMotions Migrations are going"         

            #if the count is lower than 4 then check which datastore has more freespace available
            if ((Get-Datastore -Name "DataStore1").freespacegb -ge (Get-Datastore -Name "DataStore2").freespacegb) {

                Write-Host ("`nStarting " + $_.host + " to Datastore DataStore1")
                #send an email
                Send-MailMessage -to "[email protected]" -from [email protected] -Subject ("Starting " + $_.host + " to Datastore DataStore1") -SmtpServer smtp.domain.com -DeliveryNotificationOption OnFailure, delay

                #start vMotion to DataStore1
                get-vm -Name $_.HOST | Move-VM -Datastore DataStore1 -RunAsync

            }
            else {


                Write-Host ("`nStarting " + $_.host + " to DataStore2")
                Send-MailMessage -to "[email protected]" -from [email protected] -Subject ("Starting " + $_.host + " to Datastore DataStore1") -SmtpServer smtp.domain.com -DeliveryNotificationOption OnFailure, delay

                #Start vMotion to Storage DataStore2
                get-vm -Name $_.HOST | Move-VM -Datastore DataStore2 -RunAsync

            }
        }
        else {

            #if more that 4 relocation tasks are running then wait for them to finish
            sleep 5
            Write-Host "`nWaiting for current vMotions to finish..."
            Write-Host "`nCurrent number of vMotions:"(Get-Task | ?{$_.name -eq "RelocateVM_Task" -and $_.state -eq "Running" -and $_.PercentComplete -ne "0"}).count
            do {
                #wait 60 seconds and recheck again                    
                sleep 60
            } while ((Get-Task | ?{$_.name -eq "RelocateVM_Task" -and $_.state -eq "Running"-and $_.PercentComplete -ne "0"}).count -ge "4")

                #Repeate the above process when vMotion tasks go lower than 4
                if ((Get-Task | ?{$_.name -eq "RelocateVM_Task" -and $_.state -eq "Running" -and $_.PercentComplete -ne "0"}).count -lt "4") {

                Write-Host "`nLess than 4 vMotions Migrations are going"         

                if ((Get-Datastore -Name "DataStore1").freespacegb -ge (Get-Datastore -Name "DataStore2").freespacegb) {

                    Write-Host ("`nStarting " + $_.host + " to Datastore1")
                    Send-MailMessage -to "[email protected]" -from [email protected] -Subject ("Starting " + $_.host + " to Datastore DataStore1") -SmtpServer smtp.domain.com -DeliveryNotificationOption OnFailure, delay

                    get-vm -Name $_.HOST | Move-VM -Datastore DataStore1 -RunAsync

                }
                else {
                    Write-Host ("`nStarting " + $_.host + " to Datastore2")
                    Send-MailMessage -to "[email protected]" -from [email protected] -Subject ("Starting " + $_.host + " to Datastore DataStore1") -SmtpServer smtp.domain.com -DeliveryNotificationOption OnFailure, delay

                    get-vm -Name $_.HOST | Move-VM -Datastore DataStore2 -RunAsync

                }
            }

        }

    }
}

#Check which VM's are on DR DataStore storage already
Import-Csv 'C:UsersAdministratorDesktopRestoreDetails Queue.csv' | foreach {

    if ((get-vm $_.host |Get-Datastore).name -ne "DataStore2" -or (get-vm $_.host |Get-Datastore).name -ne "DataStore1") {
        [array]$DRStorage =+ $_.host
    }
    else {
        [array]$NoDRStorage =+ $_.host
      }
}


Stop-Transcript

Tagged : / / / /

Server Bug Fix: Search and delete lines matching a pattern along with comments in previous line if any

Original Source Link

I have a requirement to write a shell script in csh to search and delete lines matching a pattern along with comments in previous line if any. For example if my file has the following lines

Shell script
#this is  a test
pattern1
format1
pattern2
format2
#format3
pattern3

If the search pattern is “pattern” The output should be as follows

Shell script
format1
format2

To be more precise the lines which have the pattern and the previous line if it begins with “#” should be deleted

Thanks for the help

First of all nobody should ever use csh for anything – it’s outdated and un- (not “under”) powered. Secondly, I doubt it’s up to the task. Third, it’s much more likely that awk, sed or even Perl will be a much better tool for the task.

awk '/^#/ {printf line; line=$0"n"; next} /pattern/ {line=""} ! /pattern/ {printf line; print; line=""}'

Edit: fixed script to handle comment lines correctly

Here is a one-liner solution in Perl (not in C shell). You can modify the /pattern/ regular expression in the middle.

perl -ne 'if(/^#/){$c=$_}elsif(!/pattern/){print$c,$_;$c=""}else{$c=""}' <file.in

Probably a better way to write this logically, but I think this might do it:

#!/usr/bin/perl
use strict;
use warnings;


my $previous_line = '';
while(<>) {
    if ( /pattern/ ) {
        if ( (! ($previous_line =~ /^#/)) && (! ($previous_line =~ /pattern/))) {
            print $previous_line;
        }
    } elsif (! ($previous_line =~ /pattern/)) {
        print $previous_line;
    }
    $previous_line = $_;
}
print $previous_line if not ($previous_line =~ /pattern/);

Basically, the loop is a line behind with the previous line. It says it is okay to print the previous line if:

  1. If The current line matches the pattern: Okay to print previous as long as the previous didn’t also match pattern, or it was a comment.
  2. If this line is not pattern, it is
    okay to print the previous line as
    long as it didn’t match pattern.

You can just save the code in a file and use it like: perl thefile.pl textfile_you_want_to_filter

Here’s a sed version. Some versions of sed may need parts of this separated into multiple -e clauses.

sed '$b;N;/^#.*npattern.*$/ ! {P;D}; :c; $d; s/.*n//;N;/^#.*npattern.*$/ {bc}; /^pattern/d; D' patterns

Here’s a script file version of that one-liner with comments:

#!/bin/sed -f

# Search for a combination of a comment followed by a pattern
# until that, print what you find.
$b
N
/^#.*npattern.*$/ ! {
P
D
}

:c
# Got a comment and a pattern combination in pattern space.
# At the end of the file we simply exit
$d

# Else, we keep reading lines with `N' until we
# find a different one
s/.*n//
N
/^#.*npattern.*$/ {
bc
}

# Remove standalone lines that have "pattern"
/^pattern/d

# Remove the last instance of the combination
# and go back to the top
D

This is based on the script in info sed section 4.16 “Remove All Duplicated Lines” (uniq -u).

Does it have to be shell scripted?

  1. open file with vi
  2. :g/<pattern>/d
  3. repeat as necessary for additional
    pattern-types unless you can regex
    the pattern
  4. :g/^#/d

can be effectively reproduced using sed if it has to be scripted

edit:

1.create file .sedscript:

/pattern/d
/^#/d

2.sed -f .sedscript <inputfile> > <outputfile>

This does not satisfy a requirement to delete the previous line, but your example doesn’t seem to require that functionality.

Tagged : / / /

Server Bug Fix: Bash flags based on if statements [duplicate]

Original Source Link

I am calling a bash script that has a number of flags in the following manner:

/home/username/myscript -a -b 76

So to illustrate, the -a flag does something in myscript and the -b flag sets some parameter to 76 in myscript.

How do I make these flags conditional based on some previously defined variable? For example, I only want to use the -a flag if some variable var=ON, else I don’t what to use that flag. I am looking for something like:

var=ON
/home/username/myscript 
if [ "$var" == "ON" ]; then
-a
fi
-b 76

This scheme did not work when implemented, however. What can be used to accomplish the desired result?

You can build the command in an array:

#!/bin/bash
var=ON
cmd=( /home/username/myscript )   # create array with one element
if [ "$var" == "ON" ]; then
    cmd+=( -a )                   # append to the array
fi
cmd+=( -b 76 )                    # append two elements

And then run it with:

"${cmd[@]}"

Note the quotes around the last part, and the parenthesis around the assignments above. The syntax is ugly, but it works in the face of arguments containing white space and such. (Use quotes to add arguments with spaces, e.g. cmd+=("foo bar"))

Related, with less ugly methods and ways they can fail:


In simple cases, like that one optional argument here, you could get away with the alternate value expansion:

var=x
myscript ${var:+"-a"} -b 76

Here, ${var:+foo} inserts foo if var is not empty (so var=ON, var=FALSE, var=x would all insert it), and nothing if it is empty or unset (var=, or unset var). Be careful with the usual quoting issues.

If I understand you correctly:

var=ON
if [ "$var" = "ON" ]; then
    /home/username/myscript -a -b 76
else
    /home/username/myscript -b 76
fi

Tagged : / /

Server Bug Fix: CentOS 7 custom service failure (Interactive authentication required)

Original Source Link

I am trying to setup an OFBiz fork called Scipio as a service on CentOS 7.

The service wrapper script changes the user to a dedicated one for the program. All of the program’s files are owned and in the group under that dedicated user name.

If I grant execute permissions on the script, have it sitting in a sub directory of the program, and log in as that dedicated user, and execute it directly like a standard bash script it functions perfectly. BUT, if I copy it to /etc/rc.d/init.d/scipio and attempt to execute it as another user (my normal account) using sudo, (executing “normally” or as service), it fails.

It looks like the error is something to the effect of:

failed to start service interactive authentication required

Here are the permissions (ls -l):

-rwxr-xr-x. 1 root root 4165 Jul  8 16:00 /etc/rc.d/init.d/scipio

Here’s how I like to launch it (as a sudoer):

sudo service scipio restart

Here’s the script itself:

#!/bin/sh
#####################################################################
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#####################################################################
#
# scipio       This shell script takes care of starting and stopping
#              the Scipio ERP server
#
# chkconfig: 2345 80 10
# description: Scipio ERP

# Source function library
# this does not exist in Debian/Ubuntu/etc. => see  rc.ofbiz.for.debian
# => comment out and use "echo failure" and "echo success" in place of echo_failure and echo_success (minor anyway)
. /etc/rc.d/init.d/functions

# Source networking configuration
# this does not exist in Debian/Ubuntu/etc. => see  rc.ofbiz.for.debian
. /etc/sysconfig/network

# Paths - Edit for your locations
JAVA_BINARY=$JAVA_HOME/bin/java
OFBIZ_HOME=/opt/scipio-erp
OFBIZ_LOG=$OFBIZ_HOME/runtime/logs/console.log

# VM Options
JAVA_VMOPTIONS="-Xms128M -Xmx1024M -XX:MaxPermSize=512M"

# Java arguments
JAVA_ARGS="-jar ofbiz.jar"

# *nix user ofbiz should run as (you must create this user first)
OFBIZ_USER=scipio

# OFBiz processes running
ofbizprocs() {
    OFBIZ_PROCS=`/bin/ps h -o pid,args -C java | /bin/grep -e "$JAVA_ARGS" | /bin/egrep -o "^[[:space:]]*[[:digit:]]*"`
}

# Checking user...
checkuser() {
    if [ "$USER" != "$OFBIZ_USER" ]; then
        echo failure
        echo
        echo "Only users root or $OFBIZ_USER should start/stop the application"
        exit 1
    fi
}

# Start OFBiz
start() {
    echo -n "Starting OFBiz: "
    checkuser
    ofbizprocs
    if [ "$OFBIZ_PROCS" != "" ]; then
        echo failure
        echo
        echo "OFBiz is already running..."
        return 1
    fi

    # All clear
    cd $OFBIZ_HOME
    umask 007
    /bin/rm -f $OFBIZ_LOG
    $JAVA_BINARY $JAVA_VMOPTIONS $JAVA_ARGS >>$OFBIZ_LOG 2>>$OFBIZ_LOG&
    echo success
    return 0
}

# Stop OFBiz
stop() {
    echo -n "Stopping OFBiz: "
    checkuser
    ofbizprocs
    if [ "$OFBIZ_PROCS" == "" ]; then
        echo failure
        echo
        echo "OFBiz is not running..."
        return 1
    fi

    # All clear
    cd $OFBIZ_HOME
    umask 007
    $JAVA_BINARY $JAVA_VMOPTIONS $JAVA_ARGS -shutdown >>$OFBIZ_LOG
    ofbizprocs
    if [ "$OFBIZ_PROCS" != "" ]; then
        # Let's try to -TERM
        /bin/kill -TERM $OFBIZ_PROCS
    fi
    ofbizprocs
    if [ "$OFBIZ_PROCS" != "" ]; then
        # Let's try it the hard way!
        /bin/kill -9 $OFBIZ_PROCS
    fi
    ofbizprocs
    if [ "$OFBIZ_PROCS" != "" ]; then
        echo failure
        echo
        echo "Some processes could not be stopped:"
        echo $OFBIZ_PROCS
        echo "A possible solution is to try this command once more!"
        return 1
    else
        echo success
        return 0
    fi
}

# If root is running this script, su to $OFBIZ_USER first
# Note that under Debian/Ubuntu/etc. you should use instead
# if [ "$USER" = "root" ]; then
if [ "$UID" = "0" ]; then
    exec su - $OFBIZ_USER -c "$0 $1"
fi

case "$1" in
    'start')
        start
    ;;
    'stop')
        stop
    ;;
    'restart')
        stop
        start
    ;;
    'status')
        ofbizprocs
        if [ "$OFBIZ_PROCS" == "" ]; then
            echo "OFBiz is stopped"
            exit 1
        else
            echo "OFBiz is running"
            exit 0
        fi
    ;;
    *)
        echo "Usage: $0 {start|stop|kill|restart|status|help}"
        exit 1
    ;;
esac
echo
exit $?

It seems like this is a CentOS 7 specific issue. I believe the services model changed, and these init.d style scripts aren’t the natural mechanism anymore. Maybe this is SELinux related?

Update

JAVA_HOME should be defined, as I previously ran:

export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.171-8.b10.el7_5.x86_64/jre

sudo sh -c "echo export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.171-8.b10.el7_5.x86_64/jre >> /etc/environment"     

… I tested and confirmed that is resolving in this context.

Journaled error message

-- Unit session-c16.scope has begun starting up.
Jul 09 20:56:19 SERVERNAME-XXXX scipio[27942]: Starting scipio (via systemctl):  Failed to start scipio.service: Interactive authentication required.
Jul 09 20:56:19 SERVERNAME-XXXX scipio[27942]: See system logs and 'systemctl status scipio.service' for details.
Jul 09 20:56:19 SERVERNAME-XXXX scipio[27942]: [FAILED]
Jul 09 20:56:19 SERVERNAME-XXXX su[27942]: pam_unix(su-l:session): session closed for user scipio
Jul 09 20:56:19 SERVERNAME-XXXX systemd[1]: scipio.service: control process exited, code=exited status=1
Jul 09 20:56:19 SERVERNAME-XXXX systemd[1]: Failed to start SYSV: Scipio ERP.
-- Subject: Unit scipio.service has failed

It looks to me like JAVA_HOME isn’t defined. Thus, when you try to run the script, /bin/java doesn’t exist, and it fails.

If you do it as a logged in user, you likely end up with that environment variable either defined in a rc file, or inherited from the user you were before changing to the service account.

Yes, CentOS 7 did switch to using systemd rather than initV — but an initscript like that should still work even if it’s deprecated.

Paul from Scipio ERP here. If you have any issue with the start script, it would be great if you could report it over at the Scipio ERP Community forum. We’d love to fix this for everyone!

That being said, I will open up a ticket and see that we can recreate the issue.

Thank you

Tagged : / / / /

Server Bug Fix: How to determine if a bash variable is empty?

Original Source Link

What is the best way to determine if a variable in bash is empty (“”)?

I have heard that it is recommended that I do if [ "x$variable" = "x" ]

Is that the correct way? (there must be something more straightforward)

This will return true if a variable is unset or set to the empty string (“”).

if [ -z "${VAR}" ];

In Bash, when you’re not concerned with portability to shells that don’t support it, you should always use the double-bracket syntax:

Any of the following:

if [[ -z $variable ]]
if [[ -z "$variable" ]]
if [[ ! $variable ]]
if [[ ! "$variable" ]]

In Bash, using double square brackets, the quotes aren’t necessary. You can simplify the test for a variable that does contain a value to:

if [[ $variable ]]

This syntax is compatible with ksh (at least ksh93, anyway). It does not work in pure POSIX or older Bourne shells such as sh or dash.

See my answer here and BashFAQ/031 for more information about the differences between double and single square brackets.

You can test to see if a variable is specifically unset (as distinct from an empty string):

if [[ -z ${variable+x} ]]

where the “x” is arbitrary.

If you want to know whether a variable is null but not unset:

if [[ -z $variable && ${variable+x} ]]

A variable in bash (and any POSIX-compatible shell) can be in one of three states:

  • unset
  • set to the empty string
  • set to a non-empty string

Most of the time you only need to know if a variable is set to a non-empty string, but occasionally it’s important to distinguish between unset and set to the empty string.

The following are examples of how you can test the various possibilities, and it works in bash or any POSIX-compatible shell:

if [ -z "${VAR}" ]; then
    echo "VAR is unset or set to the empty string"
fi
if [ -z "${VAR+set}" ]; then
    echo "VAR is unset"
fi
if [ -z "${VAR-unset}" ]; then
    echo "VAR is set to the empty string"
fi
if [ -n "${VAR}" ]; then
    echo "VAR is set to a non-empty string"
fi
if [ -n "${VAR+set}" ]; then
    echo "VAR is set, possibly to the empty string"
fi
if [ -n "${VAR-unset}" ]; then
    echo "VAR is either unset or set to a non-empty string"
fi

Here is the same thing but in handy table form:

                        +-------+-------+-----------+
                VAR is: | unset | empty | non-empty |
+-----------------------+-------+-------+-----------+
| [ -z "${VAR}" ]       | true  | true  | false     |
| [ -z "${VAR+set}" ]   | true  | false | false     |
| [ -z "${VAR-unset}" ] | false | true  | false     |
| [ -n "${VAR}" ]       | false | false | true      |
| [ -n "${VAR+set}" ]   | false | true  | true      |
| [ -n "${VAR-unset}" ] | true  | false | true      |
+-----------------------+-------+-------+-----------+

The ${VAR+foo} construct expands to the empty string if VAR is unset or to foo if VAR is set to anything (including the empty string).

The ${VAR-foo} construct expands to the value of VAR if set (including set to the empty string) and foo if unset. This is useful for providing user-overridable defaults (e.g., ${COLOR-red} says to use red unless the variable COLOR has been set to something).

The reason why [ x"${VAR}" = x ] is often recommended for testing whether a variable is either unset or set to the empty string is because some implementations of the [ command (also known as test) are buggy. If VAR is set to something like -n, then some implementations will do the wrong thing when given [ "${VAR}" = "" ] because the first argument to [ is erroneously interpreted as the -n operator, not a string.

-z is a the best way.

Another options I’ve used is to set a variable, but it can be overridden by another variable eg

export PORT=${MY_PORT:-5432}

If the $MY_PORT variable is empty, then PORT gets set to 5432, otherwise PORT is set to the value of MY_PORT. Note the syntax include the colon and dash.

If you’re interested in distinguishing the cases of set-empty versus unset status, look at the -u option for bash:

$ set -u
$ echo $BAR
bash: BAR: unbound variable
$ [ -z "$BAR" ] && echo true
bash: BAR: unbound variable
$ BAR=""
$ echo $BAR

$ [ -z "$BAR" ] && echo true
true

An alternate I’ve seen to [ -z "$foo" ] is the following, however I’m not sure why people use this method, anyone know?

[ "x${foo}" = "x" ]

Anyway if you’re disallowing unset variables (either by set -u or set -o nounset), then you’ll run into trouble with both of those methods. There’s a simple fix to this:

[ -z "${foo:-}" ]

Note: this will leave your variable undef.

The question asks how to check if a variable is an empty string and the best answers are already given for that.
But I landed here after a period passed programming in php and what I was actually searching was a check like the empty function in php working in a bash shell.
After reading the answers I realized I was not thinking properly in bash, but anyhow in that moment a function like empty in php would have been soooo handy in my bash code.
As I think this can happen to others, I decided to convert the php empty function in bash

According to the php manual:
a variable is considered empty if it doesn’t exist or if its value is one of the following:

  • “” (an empty string)
  • 0 (0 as an integer)
  • 0.0 (0 as a float)
  • “0” (0 as a string)
  • an empty array
  • a variable declared, but without a value

Of course the null and false cases cannot be converted in bash, so they are omitted.

function empty
{
    local var="$1"

    # Return true if:
    # 1.    var is a null string ("" as empty string)
    # 2.    a non set variable is passed
    # 3.    a declared variable or array but without a value is passed
    # 4.    an empty array is passed
    if test -z "$var"
    then
        [[ $( echo "1" ) ]]
        return

    # Return true if var is zero (0 as an integer or "0" as a string)
    elif [ "$var" == 0 2> /dev/null ]
    then
        [[ $( echo "1" ) ]]
        return

    # Return true if var is 0.0 (0 as a float)
    elif [ "$var" == 0.0 2> /dev/null ]
    then
        [[ $( echo "1" ) ]]
        return
    fi

    [[ $( echo "" ) ]]
}

Example of usage:

if empty "${var}"
    then
        echo "empty"
    else
        echo "not empty"
fi

Demo:
the following snippet:

#!/bin/bash

vars=(
    ""
    0
    0.0
    "0"
    1
    "string"
    " "
)

for (( i=0; i<${#vars[@]}; i++ ))
do
    var="${vars[$i]}"

    if empty "${var}"
        then
            what="empty"
        else
            what="not empty"
    fi
    echo "VAR "$var" is $what"
done

exit

outputs:

VAR "" is empty
VAR "0" is empty
VAR "0.0" is empty
VAR "0" is empty
VAR "1" is not empty
VAR "string" is not empty
VAR " " is not empty

Having said that in a bash logic the checks on zero in this function can cause side problems imho, anyone using this function should evaluate this risk and maybe decide to cut those checks off leaving only the first one.

the entire if-then and -z are unnecessary.

[ "$foo" ] && echo "foo is not empty"
[ "$foo" ] || echo "foo is indeed empty"

My 5 cents: there is also a shorter syntax than if ..., this one:

VALUE="${1?"Usage: $0 value"}"

This line will set VALUE if an argument has been supplied and will print an error message prepended with the script line number in case of an error (and will terminate the script execution).

Another example can be found in the abs-guide (search for «Example 10-7»).

This is true exactly when $FOO is set and empty:

[ "${FOO+x}" = x ] && [ -z "$FOO" ]

Personally prefer more clear way to check :

if [ "${VARIABLE}" == "" ]; then
  echo VARIABLE is empty
else
  echo VARIABLE is not empty
fi

oneliner extension of duffbeer703‘s solution:

#! /bin/bash
[ -z "$1" ] || some_command_that_needs_$1_parameter

Not an exact answer, but ran into this trick. If the string you’re looking for comes from “a command” then you can actually store the command in an env. variable and then execute it every time for the if statement, then no brackets required!

For example this command, which determines if you’re on debian:

grep debian /proc/version

full example:

IS_DEBIAN="grep -i debian /proc/version"

if $IS_DEBIAN; then
  echo 'yes debian'
else
  echo 'non debian'
fi

So this is like an indirect way (rerunning it every time) to check for an empty string (it happens to be checking for error response from the command, but it also happens to be returning an empty string).

Tagged : /

Server Bug Fix: How to check that all ZFS snapshots within a pool are without holds before destroying that pool

Original Source Link

Question

Already I can check each snapshot of a filesystem individually, manually.

I would prefer to check all at once (all with a single command or script). Please:

  • can that be done with a script?

The answer should be good for file systems with a space within the name.

Background

From the man page for zfs(8):

zfs holds [-H] [-r] snapshot…

… -r Specifies that a hold with the given tag is applied recursively to the snapshots of all descendent file systems.


I wondered whether recent snapshots are treated as descendants of an older snapshot. No:

Last login: Sat Dec  8 09:02:26 on ttys003
macbookpro08-centrim:~ gjp22$ zfs holds -r [email protected]
NAME                     TAG  TIMESTAMP
macbookpro08-centrim:~ gjp22$ zfs holds -r [email protected]
NAME                     TAG                                           TIMESTAMP
[email protected]  problem with LocalStorage for WOT for Safari  Mon Oct 29  6:44 2012
macbookpro08-centrim:~ gjp22$ zfs hold experiment [email protected]
macbookpro08-centrim:~ gjp22$ zfs holds -r [email protected]
NAME                     TAG                                           TIMESTAMP
[email protected]  problem with LocalStorage for WOT for Safari  Mon Oct 29  6:44 2012
macbookpro08-centrim:~ gjp22$ zfs holds -r [email protected]
NAME                     TAG         TIMESTAMP
[email protected]  experiment  Sat Dec  8  9:04 2012
macbookpro08-centrim:~ gjp22$ zfs holds -r [email protected]
NAME                     TAG                                           TIMESTAMP
[email protected]  problem with LocalStorage for WOT for Safari  Mon Oct 29  6:44 2012
macbookpro08-centrim:~ gjp22$ 

Clarification

I do not plan to combine both checks and destruction in a single command or script. This question is essentially about the checks.

Not sure about how this looked back in 2012 but now you can check the userrefs property:

zfs get userrefs

To list all holds in all pools:

zfs get -Ht snapshot userrefs | grep -v $'t'0 | cut -d $'t' -f 1 | tr 'n' '' | xargs -0 zfs holds

For a pool with a single file system

zfs list -H -r -d 1 -t snapshot -o name nameoffilesystem | xargs zfs holds

– that is, without -r recursion to the right of the pipe.

Credit to calmh in irc://irc.freenode.net/#zfs

Working example

For a file system with no space in its name:

macbookpro08-centrim:~ gjp22$ zfs list -H -r -d 1 -t snapshot -o name gjp22 | xargs zfs holds
load: 4.82  cmd: zfs 43038 running 0.59u 3.28s
NAME                     TAG                                           TIMESTAMP
[email protected]  problem with LocalStorage for WOT for Safari  Mon Oct 29  6:44 2012
[email protected]  experiment                                    Sat Dec  8  9:04 2012

There was one ControlT to see how things were running.

For completeness, I should state that there is a child of gjp22. But I guess that this example (without attention to descendants) does prove the effectiveness of the command.

Non-working examples

For a file system named Pocket Time Machine (spaces within its name), neither of the following commands succeeds:

zfs list -H -r -d 1 -t snapshot -o name "tall/backups/zhandy/Pocket Time Machine" | xargs zfs holds

zfs list -H -r -d 1 -t snapshot -o name tall/backups/zhandy/Pocket Time Machine | xargs zfs holds

Output:

'tall/backups/zhandy/Pocket' is not a snapshot
'Time' is not a snapshot
cannot open 'tall/backups/zhandy/Pocket': dataset does not exist
cannot open 'Time': dataset does not exist
cannot open '[email protected]': dataset does not exist
cannot open 'tall/backups/zhandy/Pocket': dataset does not exist
cannot open 'Time': dataset does not exist
cannot open '[email protected]': dataset does not exist

… and so on.

This is implicitly a question within an answer, sorry … someone with good command line knowledge (not me) might be able to smarten this answer without me spinning off to a separate question. I’ll seek advice in chat.

For a pool with multiple file systems

zfs list -H -r -d 1 -t snapshot -o name nameoffilesystematroot | xargs -n1 zfs holds -H -r

Credit to calmh in irc://irc.freenode.net/#zfs but I’m not sure whether the syntax needs a little more work.

For me, with a simple file system hierarchy (only one child) and relatively few snapshots (currently seventeen of the child), the command seems to not reach a conclusion. Example:

macbookpro08-centrim:~ gjp22$ zfs list -H -r -d 1 -t snapshot -o name gjp22 | xargs zfs holds -r
load: 4.94  cmd: zfs 39152 running 17.80u 112.52s
load: 3.73  cmd: zfs 39152 running 55.01u 349.29s
load: 3.15  cmd: zfs 39152 running 167.48u 1061.47s
load: 4.59  cmd: zfs 39152 running 267.57u 1697.49s
load: 5.19  cmd: zfs 39152 running 372.19u 2355.99s
load: 5.29  cmd: zfs 39152 running 432.89u 2736.79s

Borrowing from the other answer, with attention to the child file system alone:

macbookpro08-centrim:~ gjp22$ zfs list -H -r -d 1 -t snapshot -o name gjp22/intrigue | xargs zfs holds
NAME                              TAG  TIMESTAMP
macbookpro08-centrim:~ gjp22$ 

– and that output is almost immediate.

ZFS here is ZEVO Community Edition 1.1.1.

The following command will show all snapshots of [pool] (<-replace this with your pool name) that have holds

zfs list -H -o name -t snapshot -r pool | xargs -n1 zfs holds -H

the properties will be listed as property:stuff

with that information we can free the snapshots..

zfs list -H -o name -t snapshot -r pool | xargs -n1 zfs holds -H | awk '{print $1}' | xargs -n1 zfs release property:stuff

(replace ‘property:stuff’ with whatever is holding your dataset)

..and finally delete them

zfs destroy -r [pool]/[dataset][@snapshot]

Tagged : / /